﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BYSerial.Base;

using System.Collections.ObjectModel;
using System.IO;
using System.IO.Ports;
using System.Threading;
using BYSerial.Models;
using System.Windows;
using System.Windows.Media;
using BYSerial.Util;
using BYSerial.Views;
using System.Windows.Documents;
using BYSerial.TCPHelper;
using System.Net;
using System.Net.Sockets;
using System.Windows.Controls;
using Newtonsoft.Json.Linq;
using BYSerial.Properties;
using System.Diagnostics;
using System.ComponentModel;
using System.Text.RegularExpressions;
using WinForms = System.Windows.Forms;

namespace BYSerial.ViewModels
{
    public class MainWindowViewModel : NotificationObject
    {
        public SerialPort _serialPort;
        private readonly List<string> _messageBuffer = new List<string>();
        private readonly Queue<string> _productionReceiveLines = new Queue<string>();
        private readonly Dictionary<string, string> _pendingBurnTargets = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        private readonly object _burnStatusLock = new object();
        private bool _burnVerificationFailed;
        private readonly object _messageLock = new object();
        private const int MaxDisplayMessages = 1000;
        private const int MaxProductionReceiveLines = 4096;
        private const string BurnToolFolderName = "BurnFW";
        private const string DldToolExecutableName = "dldtool.exe";
        private const string ProgrammerDualFileName = "programmer1700_dual_20241212.bin";
        private const string ProgrammerSingleFileName = "programmer1700_single_0910.bin";
        private const string ProgrammerEmmcFileName = "programmer_1700_evbze7_1v1_emmc.bin";
        private const string RemapFileName = "ota_boot_remap_1700_ze7_glass.bin";
        private const string BootInfoFileName = "remap_ctrl_a_noBurnAddr.bin";
        private const string OtaFileName = "ota_boot_watch.bin";
        private const string RtosFileName = "best1700_glass.bin";
        private const string CpbthFileName = "cp_subsys_bth.bin";
        private const string HifiFileName = "best1700_watch_opt1.bin";
        private const string M33C0FileName = "bth_core.bin";
        private const string FatfsFileName = "norfs.bin";
        private const string EmmcFileName = "mmcfs.bin";
        private const string FlashPatternFileName = "seq_pattern.bin";
        private static readonly HashSet<string> FirmwareBinFileNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            RtosFileName,
            HifiFileName,
            CpbthFileName,
            M33C0FileName,
            FatfsFileName,
            EmmcFileName,
            OtaFileName
        };
        private static readonly string[] NoAppFileCandidates = { "noapp_test.bin", "noapp_test-pd.bin" };
        private static readonly string[] PlatformTestFileCandidates = { "platform_tester.bin" };
        private static readonly string[] PlatformM55FileCandidates = { "cp_subsys.bin", "cp_subsys_bth.bin" };
        private static readonly string[] FactoryInfoTokens = { "--btaddr", "22601F040E80", "--bleaddr", "22601F040E81", "--blename", "YT_CAMERA-LE", "--btname", "YT_CAMERA", "--btcalib", "127" };

        public MainWindowViewModel()
        {
            Init();
        }
        private void Init()
        {
            try
            {                
                GlobalPara.GetLocSet();
                if (GlobalPara.HisCfg.his != null)
                {
                    for (int i = 0; i < GlobalPara.HisCfg.his.Count; i++)
                    {
                        SendTxtHistory.Add(GlobalPara.HisCfg.his[i]);
                    }
                }
                else
                {
                    GlobalPara.HisCfg.his = new List<string>();
                }
                FastCmdList = GlobalPara.FastCfg.FastCmds;
                foreach(FastCmdModel fcm in  FastCmdList)
                {
                    if(fcm.SendCmd==null)
                    {
                        fcm.SendCmd = SendCommandByFast;
                    }
                }
                #region
                // SerialPortList = new ObservableCollection<string>(SerialPort.GetPortNames().ToList());
                //if (SerialPortList.Count > 0)
                //{
                //    _ComPortState = SerialPortList[PortNameIndex] + " ClOSED";
                //}
                //else
                //{
                //    ComPortState = "Not Detected SerialPort";
                //    ComPortStateColor = GlobalPara.RedBrush;
                //}
                #endregion

                DataBitsIndex = 3;
                ReceivePara = GlobalPara.ReceivePara;
                if (ReceivePara != null)
                {
                    ReceivePara.PropertyChanged += ReceivePara_PropertyChanged;
                }
                SendPara = GlobalPara.SendPara;
                LogPara = GlobalPara.LogPara;
                DisplayPara = GlobalPara.DisplayPara;
                InitializeBurnFirmwareOptions();
                BurnDirectoryPath = DetectDefaultBurnDirectory();
                ChangeLang(GlobalPara.MyCfg.Language);
                RefreshBurnFirmwareOptionTexts();
                BurnModeStatusText = GetLangString("burnModeStatusIdle", "Idle");
                BurnModeStatusBrush = Brushes.Gray;
                BackDetectSerialPortChange();
                //UpdateSerialPortList();
                ProductionSn = "CA420120251112A00001";
                ProductionBluetoothName = "REMI01";
                ProductionBluetoothAddress = "22:60:1F:04:0E:80";
                ProductionBleName = "REMI01-LE";
                ProductionBleAddress = "22:60:1F:04:0E:81";
                ProductionSnStepIndex = 0;
                ProductionBluetoothAddressStepIndex = 0;
                ProductionBleAddressStepIndex = 0;

                #region 命令绑定
                OnSendCommand = new DelegateCommand();
                OnSendCommand.ExecuteAction = new Action<object>(OnSend);
                OnLogCommand = new DelegateCommand();
                OnLogCommand.ExecuteAction = new Action<object>(OnLogClick);
                OnStartCommand = new DelegateCommand();
                OnStartCommand.ExecuteAction = new Action<object>(OnStartClick);
                OnPauseCommand = new DelegateCommand();
                OnPauseCommand.ExecuteAction = new Action<object>(OnPauseClick);
                OnStopCommand = new DelegateCommand();
                OnStopCommand.ExecuteAction = new Action<object>(OnStopClick);
                OnClearCommand = new DelegateCommand();
                OnClearCommand.ExecuteAction = new Action<object>(OnClearClick);
                OnHideLeftCommand = new DelegateCommand();
                OnHideLeftCommand.ExecuteAction = new Action<object>(OnHideLeft);
                ClearReadFieldsCommand = new DelegateCommand();
                ClearReadFieldsCommand.ExecuteAction = new Action<object>(ClearReadFields);
                ClearBurnFieldsCommand = new DelegateCommand();
                ClearBurnFieldsCommand.ExecuteAction = new Action<object>(ClearBurnFields);
                ManualSnIncreaseCommand = new DelegateCommand();
                ManualSnIncreaseCommand.ExecuteAction = new Action<object>(_ => ManualSnStep(1));
                ManualSnDecreaseCommand = new DelegateCommand();
                ManualSnDecreaseCommand.ExecuteAction = new Action<object>(_ => ManualSnStep(-1));
                ManualBtAddressIncreaseCommand = new DelegateCommand();
                ManualBtAddressIncreaseCommand.ExecuteAction = new Action<object>(_ => ManualAddressStep(ref _productionBluetoothAddress, 1, nameof(ProductionBluetoothAddress)));
                ManualBtAddressDecreaseCommand = new DelegateCommand();
                ManualBtAddressDecreaseCommand.ExecuteAction = new Action<object>(_ => ManualAddressStep(ref _productionBluetoothAddress, -1, nameof(ProductionBluetoothAddress)));
                ManualBleAddressIncreaseCommand = new DelegateCommand();
                ManualBleAddressIncreaseCommand.ExecuteAction = new Action<object>(_ => ManualAddressStep(ref _productionBleAddress, 1, nameof(ProductionBleAddress)));
                ManualBleAddressDecreaseCommand = new DelegateCommand();
                ManualBleAddressDecreaseCommand.ExecuteAction = new Action<object>(_ => ManualAddressStep(ref _productionBleAddress, -1, nameof(ProductionBleAddress)));
                ResetBurnFieldsCommand = new DelegateCommand();
                ResetBurnFieldsCommand.ExecuteAction = new Action<object>(_ => ResetBurnFields());

                #endregion

                #region 菜单命令
                ChangeToChCmd = new DelegateCommand();
                ChangeToChCmd.ExecuteAction = new Action<object>(ChangeToCh);
                ChangeToEnCmd = new DelegateCommand();
                ChangeToEnCmd.ExecuteAction = new Action<object>(ChangeToEn);
                SwitchToDebugModeCmd = new DelegateCommand();
                SwitchToDebugModeCmd.ExecuteAction = new Action<object>(SwitchToDebugMode);
                SwitchToProductionModeCmd = new DelegateCommand();
                SwitchToProductionModeCmd.ExecuteAction = new Action<object>(SwitchToProductionMode);
                SwitchToBurnModeCmd = new DelegateCommand();
                SwitchToBurnModeCmd.ExecuteAction = new Action<object>(SwitchToBurnMode);
                ShowToolBoxCmd = new DelegateCommand();
                ShowToolBoxCmd.ExecuteAction = new Action<object>(ShowToolBox);
                ShowAsciiCmd = new DelegateCommand();
                ShowAsciiCmd.ExecuteAction = new Action<object>(ShowAscii);
                ShowColorsCmd = new DelegateCommand();
                ShowColorsCmd.ExecuteAction = new Action<object>(ShowColors);
                ShowScreenColorCmd = new DelegateCommand();
                ShowScreenColorCmd.ExecuteAction = new Action<object>(ShowScreenColor);
                ShowOptionsCmd = new DelegateCommand();
                ShowOptionsCmd.ExecuteAction = new Action<object>(ShowOptions);
                RefeshComsCmd = new DelegateCommand();
                RefeshComsCmd.ExecuteAction = new Action<object>(RefeshComs);
                ShowChartShowCmd = new DelegateCommand();
                ShowChartShowCmd.ExecuteAction = new Action<object>(ShowChartShow);
                ShowChartParasCmd = new DelegateCommand();
                ShowChartParasCmd.ExecuteAction = new Action<object>(ShowChartParas);
                ShowModbusCmd=new DelegateCommand();
                ShowModbusCmd.ExecuteAction= new Action<object>(ShowModbus);
                ShowIOTToolCmd = new DelegateCommand();
                ShowIOTToolCmd.ExecuteAction = new Action<object>(ShowIOTTool);
                ApplyAutoSaveLogSetting();
                ShowHelpCmd = new DelegateCommand();
                ShowHelpCmd.ExecuteAction = new Action<object>(ShowHelp);
                ShowQuestionCmd=new DelegateCommand();
                ShowQuestionCmd.ExecuteAction = new Action<object>(ShowQuestion);
                ShowAboutCmd = new DelegateCommand();
                ShowAboutCmd.ExecuteAction = new Action<object>(ShowAbout);
                CheckUpdateCmd = new DelegateCommand();
                CheckUpdateCmd.ExecuteAction = new Action<object>(CheckUpdate);

                FastCmdDelCmd = new DelegateCommand();
                FastCmdDelCmd.ExecuteAction = new Action<object>(FastCmdDel);
                FastCmdAddCmd = new DelegateCommand();
                FastCmdAddCmd.ExecuteAction = new Action<object>(FastCmdAdd);
                StartReadCommand = new DelegateCommand();
                StartReadCommand.ExecuteAction = new Action<object>(OnStartRead);
                StartBurnCommand = new DelegateCommand();
                StartBurnCommand.ExecuteAction = new Action<object>(async _ => await OnStartBurnAsync());
                StartFirmwareFlashCommand = new DelegateCommand();
                StartFirmwareFlashCommand.ExecuteAction = new Action<object>(async _ => await StartFirmwareFlashAsync());
                ClearFirmwareBurnLogCommand = new DelegateCommand();
                ClearFirmwareBurnLogCommand.ExecuteAction = new Action<object>(ClearFirmwareBurnLog);
                SelectBurnFolderCommand = new DelegateCommand();
                SelectBurnFolderCommand.ExecuteAction = new Action<object>(SelectBurnFolder);
                OpenBurnFolderCommand = new DelegateCommand();
                OpenBurnFolderCommand.ExecuteAction = new Action<object>(OpenBurnFolder);
                #endregion
            }
            catch (Exception ex)
            {
                SetBurnProgressFinalState(GlobalPara.RedBrush);
                SetBurnProgressFinalState(GlobalPara.RedBrush);
                MessageBox.Show("Init Error,Please Check ErrLog");
                SaveLogAsync("Init Err:"+ex.ToString());
                System.Diagnostics.Process.GetCurrentProcess().Kill();
            }
           
        }

        #region 菜单命令
        public DelegateCommand ShowToolBoxCmd { get; private set; }
        private void ShowToolBox(object para)
        {
            ToolBoxWindow window = new ToolBoxWindow();
            window.Show();
        }

        public DelegateCommand ShowColorsCmd { get; private set; }
        private void ShowColors(object para)
        {
            ColorsWindow window = new ColorsWindow();
            window.Show();
        }
        public DelegateCommand ShowScreenColorCmd { get;private set; }
        private void ShowScreenColor(object para)
        {
            ScreenColorPicker window = new ScreenColorPicker();
            window.Show();
            //ColorPickerWin window = new ColorPickerWin();
            //window.Show();
        }

        public DelegateCommand ShowAsciiCmd { get; private set; }
        private void ShowAscii(object para)
        {
            AsciiCodeWindow window = new AsciiCodeWindow();
            window.Show();
        }
        public DelegateCommand ShowOptionsCmd { get; private set; }
        private void ShowOptions(object para)
        {
            OptionsWindow window = new OptionsWindow();
            window.Show();
        }
        public DelegateCommand RefeshComsCmd { get; private set; }
        private void RefeshComs(object para)
        {
            UpdateSerialPortList();
        }

        public DelegateCommand ShowHelpCmd { get; private set; }
        private void ShowHelp(object para)
        {
            try
            {
                string html = "https://gitee.com/LvYiWuHen/byserial";
                Util.FileTool.OpenUrlWithDefaultBrowser(html);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        public DelegateCommand ShowQuestionCmd{get;private set; }
        private void ShowQuestion(object para)
        {
            string html = "https://gitee.com/LvYiWuHen/byserial/issues";
            Util.FileTool.OpenUrlWithDefaultBrowser(html);
        }

        public DelegateCommand ShowAboutCmd { get; private set; }
        private void ShowAbout(object para)
        {
            AboutWindow window = new AboutWindow();
            window.Show();
        }

        public DelegateCommand CheckUpdateCmd { get; private set; }
        public void CheckUpdate(object para)
        {
            try
            {
                int i = 0;
                if (!Util.Update.InternetGetConnectedState(out i, 0))
                {
                    MessageBox.Show("无网络，检查取消", "更新提示");
                    return;
                }
                bool check = BYSerial.Util.Update.CheckUpdate(); 
                if(!check)
                {
                    MessageBox.Show("已经是最新版本，无需更新", "更新提示");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            
        }


        public DelegateCommand ChangeToChCmd { get; private set; }
        private void ChangeToCh(object para)
        {
            // TODO: 切换系统资源文件
            ChangeLang("zh-CN");
        }
        public DelegateCommand ChangeToEnCmd { get; private set; }
        private void ChangeToEn(object para)
        {
            ChangeLang("en-US");
        }
        private void ChangeLang(string lang)
        {
            if(lang == "en-US")
            {
                ResourceDictionary dict = new ResourceDictionary();
                dict.Source = new Uri(@"Assets\Language\en-US.xaml", UriKind.Relative);
                Application.Current.Resources.MergedDictionaries[0] = dict;
                if (GlobalPara.MyCfg != null)
                {
                    GlobalPara.MyCfg.Language = "en-US";
                }
            }
            else
            {
                ResourceDictionary dict = new ResourceDictionary();
                dict.Source = new Uri(@"Assets\Language\zh-CN.xaml", UriKind.Relative);
                Application.Current.Resources.MergedDictionaries[0] = dict;
                if (GlobalPara.MyCfg != null)
                {
                    GlobalPara.MyCfg.Language = "zh-CN";
                }
            }
            RefreshBurnFirmwareOptionTexts();
        }

        private bool _MenuModbusIsEnable = true;

        public bool MenuModbusIsEnable
        {
            get { return _MenuModbusIsEnable; }
            set { _MenuModbusIsEnable = value;
                RaisePropertyChanged();
            }
        }
        private bool _MenuIOTToolIsEnable = true;

        public bool MenuIOTToolIsEnable
        {
            get { return _MenuIOTToolIsEnable; }
            set
            {
                _MenuIOTToolIsEnable = value;
                RaisePropertyChanged();
            }
        }

        #endregion

        private delegate void UpdateSerialPortListDelagte();
        /// <summary>
        /// 更新串口列表
        /// </summary>
        private void UpdateSerialPortList()
        {


            ObservableCollection<string> coms = new ObservableCollection<string>(SerialPort.GetPortNames());
            coms.Add("TCP");
            bool bIsEqual = coms.SequenceEqual(SerialPortList);
            if(!bIsEqual)
            {
                SerialPortList = new ObservableCollection<string>(SerialPort.GetPortNames());
                SerialPortList.Add("TCP");
                if (SerialPortList.Count == 1)
                {
                    PortNameIndex = 0;
                }
                else if(SerialPortList.Count >= 2) 
                {
                    PortNameIndex = SerialPortList.Count-2;  //自动定位到新添加的串口号
                }
            }
            //if ((PortNameIndex == SerialPortList.Count - 1) && SendPara.IsText)
            //{
            //    SendPara.EncodingVisual = Visibility.Visible;
            //}
            //else
            //{
            //    SendPara.EncodingVisual = Visibility.Hidden;
            //}
        }
        private void BackDetectSerialPortChange()
        {
            Task task = Task.Factory.StartNew(new Action(() =>
            {
                while (true)
                {
                    if (!IsStartCan)
                    {
                        Thread.Sleep(200);
                        continue;
                    }                        
                    Thread.Sleep(500);                    
                    Application.Current.Dispatcher.BeginInvoke(new UpdateSerialPortListDelagte(UpdateSerialPortList));  
                }
            }));
        }

        private void SendCmdLoop()
        {
           Task t= new Task(() => {
                while (SendPara.IsLoop)
                {
                    Thread.Sleep(SendPara.LoopInterval);
                    if (!SendCommand(null))
                    {
                        SendPara.IsLoop = false;                        
                    }
                }
                SendCmdIsEnable = true;
               _IsLooping = false;
               FastSendCmdIsEnable = true;
            });
            t.ContinueWith(t => 
            { 
                GC.Collect(); 
            });  //解决线程占用
            t.Start();
        }

        private FlowDocument _ReciveFlowDoc=null;

        public DelegateCommand OnSendCommand { get; private set; }
        private void OnSend(object para)
        {
            if(SendPara.IsLoop)
            {
                SendCmdIsEnable = false;
                _IsLooping = true;
                FastSendCmdIsEnable=false;
                SendCmdLoop();               
            }
            else
            {
                SendCommand(para);
            }
        }
        private bool SendCommand(object para)
        {
            try
            {
                if (SendTxt.Trim() == "")
                {
                    MessageBox.Show("发送区不可为空字符", "提示");
                    return false;
                }
                string txtsend = "";
                if (SendPara.IsHex)
                {
                    //如果是HEX,清空命令中的空格
                    string[] txts = SendTxt.Split(' ');
                    string txtssend = "";
                    for (int i = 0; i < txts.Length; i++)
                    {
                        txtssend += txts[i];
                    }
                    txtsend = txtssend;
                }
                else
                {
                    //如果是ASCII,对空格不做处理
                    txtsend = SendTxt;
                } 
                if (SendPara.FormatSend)
                {
                    if (SendPara.FormatCRNL)
                    {
                        txtsend += "\r\n";
                    }
                    else if (SendPara.FormatNLCR)
                    {
                        txtsend += "\n\r";
                    }
                    else if (SendPara.FormatNewLine)
                    {
                        txtsend += "\r";
                    }
                    else if (SendPara.FormatCarReturn)
                    {
                        txtsend += "\n";
                    }
                }
                int byteNum = 0;

                if (SendPara.IsHex)
                {
                    txtsend = txtsend.Replace(" ", "").ToUpper(); //20220616格式化字符串
                    if (txtsend.Length%2!=0)
                    {
                        MessageBox.Show("输入字符长度为奇数，命令不可发送；请检查命令是否有错！\r\n一个字节至少2个字符，不足请补零","错误提示");
                        return false;
                    }
                    byte[] btSend = new byte[1];
                    if (SendPara.AutoCRC)
                    {
                        string strcrc = CommonCheck.CheckCRC16Modbus(txtsend);  
                        txtsend += strcrc; //20220616增加修复 自动计算CRC错误，并且不显示完整的命令字符串
                        btSend = Util.DataConvertUtility.HexStringToByte(txtsend);
                    }
                    else
                    {
                        btSend = Util.DataConvertUtility.HexStringToByte(txtsend);
                    }
                    if (IsSerialTest == Visibility.Visible)
                    {
                        _serialPort.Write(btSend, 0, btSend.Length);
                    }
                    else
                    {
                        if (TcpPara.bIsTcpClient)
                        {
                            _TcpClient.SendAsync(btSend);
                        }
                        else if(TcpPara.bIsTcpServer)
                        {
                            _TcpServer.SendAsync(TcpPara.TcpClients[TcpPara.SvrClientsIndex],btSend);
                        }
                    }
                    byteNum = btSend.Length;

                }
                else 
                {
                    if (IsSerialTest == Visibility.Visible)
                    {
                        //使串口支持其它编码。
                        byte[] bts=SendPara.TextEncoding.GetBytes(txtsend);
                        _serialPort.Write(bts,0,bts.Length);
                    }
                    else
                    {
                        if (TcpPara.bIsTcpClient)
                        {                   
                            _TcpClient.SendAsync(txtsend, SendPara.TextEncoding);
                        }
                        else if(TcpPara.bIsTcpServer)
                        {                            
                            _TcpServer.SendAsync(TcpPara.TcpClients[TcpPara.SvrClientsIndex], txtsend, SendPara.TextEncoding);
                        }
                    }
                    byteNum = SendPara.TextEncoding.GetBytes(txtsend).Length; // Encoding.UTF8.GetBytes(txtsend).Length;
                }

                if (ReceivePara.DisplaySend)
                {
                    if (ReceivePara.AutoFeed)
                    {
                        if (SendPara.IsHex)
                        {
                            txtsend = txtsend.Replace(" ", "");
                            txtsend = Util.DataConvertUtility.InsertFormat(txtsend, 2, " ") + "\r\n";
                        }
                        else
                        {
                            txtsend += "\r\n";
                        }
                    }
                    if (ReceivePara.DisplayTime)
                    {
                        txtsend =DateTime.Now.ToString(ReceivePara.TimeFormat) + txtsend;
                    }
                    txtsend = "[SEND]" + txtsend;
                    ShowMsgToMain(txtsend);
                    if (LogPara.SaveLogMsg)
                    {
                        SaveLogAsync(txtsend);
                    }
                }
                _SendedBytesNum += byteNum;
                SendBytesStr = "Tx: " + _SendedBytesNum + " Bytes";                
                AddSendHistory(SendTxt);
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            return false;
        }
        ///// <summary>
        ///// 快捷命令发送
        ///// </summary>
        ///// <param name="cmdstring"></param>
        ///// <returns></returns>
        //public bool SendCommandByFast(string cmdstring)
        //{
        //    try
        //    {
        //        string txtsend="";
        //        if (SendPara.IsHex)
        //        {
        //            //如果是HEX,清空命令中的空格
        //            string[] txts = cmdstring.Split(' ');
        //            string txtssend = "";
        //            for (int i = 0; i < txts.Length; i++)
        //            {
        //                txtssend += txts[i];
        //            }
        //            txtsend = txtssend;
        //        }
        //        else
        //        {
        //            //如果是ASCII,对空格不做处理
        //            txtsend = cmdstring;
        //        }

        //        if (SendPara.FormatSend)
        //        {
        //            if (SendPara.FormatCRNL)
        //            {
        //                txtsend += "\r\n";
        //            }
        //            else if (SendPara.FormatNLCR)
        //            {
        //                txtsend += "\n\r";
        //            }
        //            else if (SendPara.FormatNewLine)
        //            {
        //                txtsend += "\r";
        //            }
        //            else if (SendPara.FormatCarReturn)
        //            {
        //                txtsend += "\n";
        //            }
        //        }
        //        int byteNum = 0;

        //        if (SendPara.IsHex)
        //        {
        //            txtsend = txtsend.Replace(" ", "").ToUpper(); //20220616格式化字符串
        //            if (txtsend.Length % 2 != 0)
        //            {
        //                MessageBox.Show("输入字符长度为奇数，命令不可发送；请检查命令是否有错！\r\n一个字节至少2个字符，不足请补零", "错误提示");
        //                return false;
        //            }
        //            byte[] btSend = new byte[1];
        //            if (SendPara.AutoCRC)
        //            {
        //                string strcrc = CommonCheck.CheckCRC16Modbus(txtsend);
        //                txtsend += strcrc; //20220616增加修复 自动计算CRC错误，并且不显示完整的命令字符串
        //                btSend = Util.DataConvertUtility.HexStringToByte(txtsend);
        //            }
        //            else
        //            {
        //                btSend = Util.DataConvertUtility.HexStringToByte(txtsend);
        //            }
        //            if (IsSerialTest == Visibility.Visible)
        //            {
        //                _serialPort.Write(btSend, 0, btSend.Length);
        //            }
        //            else
        //            {
        //                if (TcpPara.bIsTcpClient)
        //                {
        //                    _TcpClient.SendAsync(btSend);
        //                }
        //                else if (TcpPara.bIsTcpServer)
        //                {
        //                    _TcpServer.SendAsync(TcpPara.TcpClients[TcpPara.SvrClientsIndex], btSend);
        //                }
        //            }
        //            byteNum = btSend.Length;

        //        }
        //        else if (SendPara.IsText || SendPara.IsUTF8)
        //        {
        //            if (IsSerialTest == Visibility.Visible)
        //            {
        //                _serialPort.Write(txtsend);
        //            }
        //            else
        //            {
        //                if (TcpPara.bIsTcpClient)
        //                {
        //                    _TcpClient.SendAsync(txtsend, SendPara.TcpTextEncoding);
        //                }
        //                else if (TcpPara.bIsTcpServer)
        //                {
        //                    _TcpServer.SendAsync(TcpPara.TcpClients[TcpPara.SvrClientsIndex], txtsend, SendPara.TcpTextEncoding);
        //                }
        //            }
        //            byteNum = SendPara.TcpTextEncoding.GetBytes(txtsend).Length; // Encoding.UTF8.GetBytes(txtsend).Length;
        //        }

        //        if (ReceivePara.DisplaySend)
        //        {
        //            if (ReceivePara.AutoFeed)
        //            {
        //                if (SendPara.IsHex)
        //                {
        //                    txtsend = txtsend.Replace(" ", "");
        //                    txtsend = Util.DataConvertUtility.InsertFormat(txtsend, 2, " ") + "\r\n";
        //                }
        //                else
        //                {
        //                    txtsend += "\r\n";
        //                }
        //            }
        //            if (ReceivePara.DisplayTime)
        //            {
        //                txtsend = DateTime.Now.ToString(ReceivePara.TimeFormat) + txtsend;
        //            }
        //            txtsend = "[SEND]" + txtsend;
        //            App.Current.Dispatcher.BeginInvoke(new Action(() => {
        //                Paragraph pg = new Paragraph();
        //                pg.Margin = new Thickness(1);
        //                pg.Padding = new Thickness(0);
        //                pg.Inlines.Add(new Run(txtsend));
        //                if (DisplayPara.FormatDisColor)
        //                {
        //                    pg.Foreground = DisplayPara.SendColor;
        //                }
        //                _ReciveFlowDoc.Blocks.Add(pg);
        //            }));

        //            if (LogPara.SaveLogMsg)
        //            {
        //                SaveLogAsync(txtsend);
        //            }
        //        }
        //        _SendedBytesNum += byteNum;
        //        SendBytesStr = "Tx: " + _SendedBytesNum + " Bytes";
        //        AddSendHistory(SendTxt);
        //        return true;
        //    }
        //    catch (Exception ex)
        //    {
        //        MessageBox.Show(ex.Message);
        //    }
        //    return false;
        //}

        #region Toolbar command

        public DelegateCommand OnLogCommand { get; private set; }

        private void OnLogClick(object parameter)
        {
            SaveLogAs();
        }

        private SolidColorBrush _LogBtnBackColor = new SolidColorBrush(Colors.Transparent);

        public SolidColorBrush LogBtnBackColor
        {
            get => _LogBtnBackColor;
            set
            {
                _LogBtnBackColor = value;
                this.RaisePropertyChanged("LogBtnBackColor");
            }
        }

        public bool AutoSaveLogEnabled
        {
            get => _autoSaveLogEnabled;
            set
            {
                if (_autoSaveLogEnabled != value)
                {
                    _autoSaveLogEnabled = value;
                    RaisePropertyChanged(nameof(AutoSaveLogEnabled));
                    ApplyAutoSaveLogSetting();
                }
            }
        }

        private void ApplyAutoSaveLogSetting()
        {
            LogPara.SaveLogMsg = _autoSaveLogEnabled;
            if (_autoSaveLogEnabled && _serialPort != null && _serialPort.IsOpen)
            {
                PrepareSerialLogFile(_serialPort.PortName);
            }
        }

        private bool _IsStartCan = true;

        public bool IsStartCan
        {
            get => _IsStartCan;
            set
            {
                _IsStartCan = value;
                this.RaisePropertyChanged("IsStartCan");
            }
        }

        private bool _IsStopCan = false;
        public bool IsStopCan
        {
            get => _IsStopCan;
            set
            {
                _IsStopCan = value;
                this.RaisePropertyChanged("IsStopCan");
            }
        }

        public DelegateCommand OnStartCommand { get; private set; }

        private void OnStartClick(object parameter)
        {
            try
            {
                //将显示收发信息的流文档传输过来。
                _ReciveFlowDoc = parameter as FlowDocument;
                RefreshFilteredMessages();

                if (_serialPort != null)
                {
                    if (_serialPort.IsOpen) _serialPort.Close();
                }


                bool bdtr = false; bool brts = false;

                if (FlowType == 1)
                {
                    brts = true;
                }
                else if (FlowType == 2)
                {
                    bdtr = true;
                }
                if(PortNameIndex==SerialPortList.Count-1)
                {
                    if(TcpPara.bIsTcpClient)
                    {
                        if((TcpPara.IP=="")||(TcpPara.Port==0))
                        {
                            MessageBox.Show("请填写正确的IP或Port", "错误提示");
                            return;
                        }
                        _TcpClient = new ClientAsync();
                        _TcpClient.Completed += _TcpClient_Completed;
                        _TcpClient.Received += _TcpClient_Received;
                        _TcpClient.ConnectAsync(TcpPara.IP, TcpPara.Port);
                        
                    }
                    else if(TcpPara.bIsTcpServer)
                    {
                        if ((TcpPara.IP == "") || (TcpPara.Port == 0))
                        {
                            MessageBox.Show("请填写正确的IP或Port", "错误提示");
                            return;
                        }
                        _TcpServer = new ServerAsync();
                        _TcpServer.Completed += _TcpServer_Completed;
                        _TcpServer.Received += _TcpServer_Received;
                        _TcpServer.StartAsync(TcpPara.IP,TcpPara.Port);
                        IsStartCan = false;
                        IsStopCan = true;
                    }
                    
                }
                else
                {
                    _serialPort = new SerialPort();
                    _serialPort.PortName = SerialPortList[PortNameIndex];
                    _serialPort.BaudRate = int.Parse( BaudRateValue); // [BaudRateIndex];
                    _serialPort.Parity = (Parity)this.Parity;
                    _serialPort.DataBits = DataBitsList[DataBitsIndex];
                    _serialPort.StopBits = (StopBits)this.StopBits;
                    _serialPort.DtrEnable = bdtr;
                    _serialPort.RtsEnable = brts;
                    _serialPort.DataReceived += _serialPort_DataReceived;
                    _serialPort.Open();
                    PrepareSerialLogFile(_serialPort.PortName);
                    ComPortState = SerialPortList[PortNameIndex] + " Opened";

                    ComPortStateColor = GlobalPara.GreenBrush;
                    IsStartCan = false;
                    IsStopCan = true;
                    IsPauseCan = true;
                    SendCmdIsEnable = true;                    
                    PauseBtnBackColor = GlobalPara.TransparentBrush;
                }
                FastSendCmdIsEnable = true;
                MenuIOTToolIsEnable = false;
                MenuModbusIsEnable = false;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                ComPortState = SerialPortList[PortNameIndex] + " Can't open!";
                ComPortStateColor = GlobalPara.RedBrush;
            }
        }

        

        public DelegateCommand OnHideLeftCommand { private set; get; }
        private void OnHideLeft(Object parameter)
        {
            if (LeftSetingVisual == Visibility.Visible)
            {
                LeftSetingVisual = Visibility.Collapsed;
            }
            else
            {
                LeftSetingVisual = Visibility.Visible;
            }
        }


        private Visibility _LeftSetingVisual = Visibility.Visible;
        public Visibility LeftSetingVisual
        {
            get => _LeftSetingVisual;
            set
            {
                _LeftSetingVisual = value;
                RaisePropertyChanged("LeftSetingVisual");
            }
        }


        private SolidColorBrush _PauseBtnBackColor = new SolidColorBrush(Colors.Transparent);
        public SolidColorBrush PauseBtnBackColor
        {
            get => _PauseBtnBackColor;
            set
            {
                _PauseBtnBackColor = value;
                this.RaisePropertyChanged("PauseBtnBackColor");
            }
        }


        public DelegateCommand OnPauseCommand { get; private set; }

        private bool _IsPauseCan = false;

        public bool IsPauseCan
        {
            get => _IsPauseCan;
            set
            {
                _IsPauseCan = value;
                this.RaisePropertyChanged("IsPauseCan");
            }
        }

        private void OnPauseClick(object parameter)
        {

            if (PauseBtnBackColor == GlobalPara.GreenBrush)
            {
                PauseBtnBackColor = GlobalPara.RedBrush;
            }
            else
            {
                PauseBtnBackColor = GlobalPara.GreenBrush;
            }
        }


        public DelegateCommand OnStopCommand { get; private set; }

        private void OnStopClick(object parameter)
        {
            SendPara.IsLoop = false;
            IsStopCan = false;
            IsStartCan = true;
            IsPauseCan = false;
            SendCmdIsEnable = false;
            FastCmdIsStartSend = false;
            FastSendCmdIsEnable = false;
            MenuIOTToolIsEnable = true;
            MenuModbusIsEnable = true;
            PauseBtnBackColor = GlobalPara.TransparentBrush;
            if(IsSerialTest==Visibility.Visible)
            {
                if (_serialPort != null)
                {
                    if (_serialPort.IsOpen) _serialPort.Close();
                    ComPortState = SerialPortList[PortNameIndex] + " Closed";
                    ComPortStateColor = new SolidColorBrush(Colors.Green);
                    FlushLogBufferIfNeeded();
                }
            }
            else if(TcpPara.bIsTcpClient)
            {
                if(_TcpClient != null)
                {
                    _TcpClient.Close();
                }
            }
            else if(TcpPara.bIsTcpServer)
            {
               if(_TcpServer!=null)  _TcpServer.Close();
                _TcpPara.TcpClients.Clear();
            }
        }
        public DelegateCommand OnClearCommand { get; private set; }
        public DelegateCommand ClearReadFieldsCommand { get; private set; }
        public DelegateCommand ClearBurnFieldsCommand { get; private set; }
        public DelegateCommand ResetBurnFieldsCommand { get; private set; }
        public DelegateCommand ManualSnIncreaseCommand { get; private set; }
        public DelegateCommand ManualSnDecreaseCommand { get; private set; }
        public DelegateCommand ManualBtAddressIncreaseCommand { get; private set; }
        public DelegateCommand ManualBtAddressDecreaseCommand { get; private set; }
        public DelegateCommand ManualBleAddressIncreaseCommand { get; private set; }
        public DelegateCommand ManualBleAddressDecreaseCommand { get; private set; }

        private bool _burnButtonEnabled = true;
        public bool BurnButtonEnabled
        {
            get => _burnButtonEnabled;
            set
            {
                if (_burnButtonEnabled != value)
                {
                    _burnButtonEnabled = value;
                    RaisePropertyChanged();
                }
            }
        }

        private void OnClearClick(object parameter)
        {
            lock (_messageLock)
            {
                _messageBuffer.Clear();
            }
            App.Current.Dispatcher.BeginInvoke(new Action(() => {
                _ReciveFlowDoc?.Blocks.Clear();
            }));
            ClearProductionReceiveLog();
            _SendedBytesNum = 0;
            _ReceivedBytesNum = 0;
            SendBytesStr = "Tx: 0 Bytes";
            ReceiveBytesStr = "Rx: 0 Bytes";
        }

        private void ClearReadFields(object obj)
        {
            ProductionReadSn = string.Empty;
            ProductionReadBluetoothName = string.Empty;
            ProductionReadBluetoothAddress = string.Empty;
            ProductionReadBleName = string.Empty;
            ProductionReadBleAddress = string.Empty;
        }

        private void ClearBurnFields(object obj)
        {
            ProductionSn = string.Empty;
            ProductionBluetoothName = string.Empty;
            ProductionBluetoothAddress = string.Empty;
            ProductionBleName = string.Empty;
            ProductionBleAddress = string.Empty;
            lock (_burnStatusLock)
            {
                _pendingBurnTargets.Clear();
                _burnVerificationFailed = false;
            }
            UpdateBurnStatus(string.Empty, Brushes.Transparent);
        }

        private void ResetBurnFields()
        {
            ProductionSn = "CA420120251112A00001";
            ProductionBluetoothName = "REMI01";
            ProductionBluetoothAddress = "22:60:1F:04:0E:80";
            ProductionBleName = "REMI01-LE";
            ProductionBleAddress = "22:60:1F:04:0E:81";
            lock (_burnStatusLock)
            {
                _pendingBurnTargets.Clear();
                _burnVerificationFailed = false;
            }
            UpdateBurnStatus(string.Empty, Brushes.Transparent);
        }

        #endregion

        #region Port Operate
        private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                if (!_IsPauseCan) return;
                Thread.Sleep(50);
                int nums = _serialPort.BytesToRead;
                byte[] bytes = new byte[nums];
                _serialPort.Read(bytes, 0, nums);
                string receivestr = "";
                if (ReceivePara.IsHex)
                {
                    receivestr = DataConvertUtility.BytesToHexStrWithSpace(bytes);
                }
                else
                {
                    //使串口通讯支持中文20230421
                    receivestr = ReceivePara.TextEncoding.GetString(bytes); // Encoding.ASCII.GetString(bytes); 
                    ProcessDevInfoPayload(receivestr);
                    ProcessBurnFeedback(receivestr);
                }
                AddDataToChart(receivestr);
                if (ReceivePara.AutoFeed)
                {
                    receivestr += "\r\n";
                }
                if (ReceivePara.DisplayTime)
                {
                    receivestr = DateTime.Now.ToString(ReceivePara.TimeFormat) + receivestr;
                }                
                receivestr = "[REC]" + receivestr;
                ShowMsgToMain(receivestr);
                _ReceivedBytesNum += bytes.Length;
                ReceiveBytesStr = "Rx: " + _ReceivedBytesNum + " Bytes";

                if (LogPara.SaveLogMsg)
                {
                    SaveLogAsync(receivestr);
                }
            }
            catch
            {
                ComPortState = SerialPortList[PortNameIndex] + " Recive Process Error!";
                ComPortStateColor = GlobalPara.RedBrush;
            }
        }
        #endregion



        #region PortParaGetSet
        private ObservableCollection<string> _SerialPortList = new ObservableCollection<string>();

        public ObservableCollection<string> SerialPortList
        {
            get => _SerialPortList;
            set
            {
                _SerialPortList = value;
                this.RaisePropertyChanged("SerialPortList");
            }
        }

        private string _burnPortText = string.Empty;
        public string BurnPortText
        {
            get => _burnPortText;
            set
            {
                string normalized = (value ?? string.Empty).Trim();
                if (!string.Equals(_burnPortText, normalized, StringComparison.OrdinalIgnoreCase))
                {
                    _burnPortText = normalized;
                    RaisePropertyChanged();
                }
            }
        }
       
        private int _PortNameIndex = 0;

        public int PortNameIndex
        {
            get => _PortNameIndex;
            set
            {
                if (SerialPortList == null || SerialPortList.Count == 0)
                {
                    _PortNameIndex = value;
                    this.RaisePropertyChanged("PortNameIndex");
                    IsSerialTest = Visibility.Visible;
                    return;
                }

                if (value < 0)
                {
                    value = 0;
                }
                else if (value >= SerialPortList.Count)
                {
                    value = SerialPortList.Count - 1;
                }

                _PortNameIndex = value;
                this.RaisePropertyChanged("PortNameIndex");

                bool isTcpSelected = SerialPortList.Count > 0 &&
                                     value == SerialPortList.Count - 1 &&
                                     string.Equals(SerialPortList[SerialPortList.Count - 1], "TCP", StringComparison.OrdinalIgnoreCase);

                if (IsDebugMode && isTcpSelected)
                {
                    IsSerialTest = Visibility.Collapsed;
                }
                else
                {
                    IsSerialTest = Visibility.Visible;
                }
            }
        }

        private string _BaudRateValue;
        /// <summary>
        /// 串口通讯速率
        /// </summary>
        public string BaudRateValue
        {
            get { return _BaudRateValue; }
            set {
                _BaudRateValue = value;
                RaisePropertyChanged("BaudRateValue");
            }
        }


        private ObservableCollection<string> _BaudRateList = new ObservableCollection<string>() { "9600", "19200", "38400", "57600", "115200", "230400", "460800", "921600", "1500000","3000000"};

        public ObservableCollection<string> BaudRateList
        {
            get => _BaudRateList;
            set
            {
                _BaudRateList = value;
                this.RaisePropertyChanged("BaudRateList");
            }
        }

        private int _BaudRateIndex = 9;

        public int BaudRateIndex
        {
            get => _BaudRateIndex;
            set
            {
                _BaudRateIndex = value;
                this.RaisePropertyChanged("BaudRateIndex");                
            }
        }

        private ObservableCollection<int> _DataBitsList = new ObservableCollection<int>() { 5, 6, 7, 8 };

        public ObservableCollection<int> DataBitsList
        {
            get => _DataBitsList;
            set
            {
                _DataBitsList = value;
                this.RaisePropertyChanged("DataBitsList");
            }
        }

        private int _DataBitsIndex = 0;
        public int DataBitsIndex
        {
            get => _DataBitsIndex;
            set
            {
                _DataBitsIndex = value;
                this.RaisePropertyChanged("DataBitsIndex");
            }
        }

        private int _Parity = 0;
        public int Parity
        {
            get => _Parity;
            set
            {
                _Parity = value;
                this.RaisePropertyChanged("Parity");
            }
        }

        private int _StopBits = 1;
        public int StopBits
        {
            get => _StopBits;
            set
            {
                _StopBits = value;
                this.RaisePropertyChanged("StopBits");
            }
        }

        private int _FlowType = 0;
        public int FlowType
        {
            get => _FlowType;
            set
            {
                _FlowType = value;
                this.RaisePropertyChanged("FlowType");
            }
        }
        #endregion

        #region PortSendReceiveState
        private string _ComPortState = "";

        public string ComPortState
        {
            get => _ComPortState;
            set
            {
                _ComPortState = value;
                this.RaisePropertyChanged("ComPortState");
            }
        }
        private SolidColorBrush _ComPortStateColor = new SolidColorBrush(Colors.Red);
        public SolidColorBrush ComPortStateColor
        {
            get => _ComPortStateColor;
            set
            {
                _ComPortStateColor = value;
                this.RaisePropertyChanged("ComPortStateColor");
            }
        }
        /// <summary>
        /// �Ѿ����͵��ֽ���
        /// </summary>
        private long _SendedBytesNum = 0;

        private string _SendBytesStr = "Tx:0 Bytes";
        public string SendBytesStr
        {
            get => _SendBytesStr;
            set
            {
                _SendBytesStr = value;
                this.RaisePropertyChanged("SendBytesStr");
            }
        }

        private SolidColorBrush _SendBytesColor = new SolidColorBrush(Colors.Green);
        public SolidColorBrush SendBytesColor
        {
            get => _SendBytesColor;
            set
            {
                _SendBytesColor = value;
                this.RaisePropertyChanged("SendBytesColor");
            }
        }
        /// <summary>
        /// �Ѿ����յ��ֽ���
        /// </summary>
        private long _ReceivedBytesNum = 0;
        private string _ReceiveBytesStr = "Rx:0 Bytes";
        public string ReceiveBytesStr
        {
            get => _ReceiveBytesStr;
            set
            {
                _ReceiveBytesStr = value;
                this.RaisePropertyChanged("ReceiveBytesStr");
            }
        }
        private SolidColorBrush _ReceiveBytesColor = new SolidColorBrush(Colors.BlueViolet);
        public SolidColorBrush ReceiveBytesColor
        {
            get => _ReceiveBytesColor;
            set
            {
                _ReceiveBytesColor = value;
                this.RaisePropertyChanged("ReceiveBytesColor");
            }
        }
        #endregion

        #region 绑定属性

        private int _SendEncodingIndex = 0;
        /// <summary>
        /// 文本发送时字符串编码格式字符串
        /// </summary>
        public int SendEncodingIndex
        {
            get { return _SendEncodingIndex; }
            set
            {
                _SendEncodingIndex= value;                           
                RaisePropertyChanged("SendEncodingIndex");
                string[] strs = (string[])Application.Current.FindResource("EncodingArray");
                SendPara.TextEncoding = Encoding.GetEncoding(strs[value]);
            }
        }
        private int _ReceiveEncodingIndex = 0;
        /// <summary>
        /// 文本接收时字符串编码格式编号
        /// </summary>
        public int ReceiveEncodingIndex
        {
            get { return _ReceiveEncodingIndex; }
            set
            {
                _ReceiveEncodingIndex = value;
                RaisePropertyChanged("ReceiveEncodingIndex");
                string[] strs = (string[])Application.Current.FindResource("EncodingArray");
                ReceivePara.TextEncoding = Encoding.GetEncoding(strs[value]);

            }
        }

        private Visibility _IsSerialTest = Visibility.Visible;
        /// <summary>
        /// 是否是串口测试
        /// </summary>
        public Visibility IsSerialTest
        {
            get { return _IsSerialTest; }
            set
            {
                _IsSerialTest = value;
                if (_IsSerialTest != Visibility.Visible)
                {
                    TcpPara.IsTcpTest=Visibility.Visible;
                }
                else
                {
                    TcpPara.IsTcpTest=Visibility.Collapsed;
                }
                RaisePropertyChanged("IsSerialTest");
            }
        }
      
        private DisplayPara _DisplayPara;
        public DisplayPara DisplayPara
        {
            get => _DisplayPara;
            set
            {
                _DisplayPara = value;
                RaisePropertyChanged("DisplayPara");
            }
        }

        private LogPara _LogPara;

        public LogPara LogPara
        {
            get => _LogPara;
            set
            {
                _LogPara = value;
                this.RaisePropertyChanged("LogPara");
            }
        }


        private ReceivePara _ReceivePara;
        public ReceivePara ReceivePara
        {
            get => _ReceivePara;
            set
            {
                _ReceivePara = value;
                this.RaisePropertyChanged("ReceivePara");
            }

        }

        private SendPara _SendPara;
        public SendPara SendPara
        {
            get => _SendPara;
            set
            {
                _SendPara = value;
                this.RaisePropertyChanged("SendPara");
            }

        }

        private string _SendTxt = "";

        public string SendTxt
        {
            get => _SendTxt;
            set
            {
                _SendTxt = value;
                this.RaisePropertyChanged("SendTxt");
            }
        }
        private ObservableCollection<string> _SendTxtHistory = new ObservableCollection<string>();

        public ObservableCollection<string> SendTxtHistory
        {
            get => _SendTxtHistory;
            set
            {
                _SendTxtHistory = value;
                this.RaisePropertyChanged("SendTxtHistory");
            }
        }

        private void AddSendHistory(string str)
        {
            try
            {
                if (SendTxtHistory.Count > 20)
                {                    
                    App.Current.Dispatcher.Invoke(new Action(() => {
                        SendTxtHistory.RemoveAt(0);
                        SendTxtHistory.Add(str);
                    }));
                }
                if (SendTxtHistory.Contains(str)) return;
                App.Current.Dispatcher.BeginInvoke(new Action(() => {
                    SendTxtHistory.Add(str);
                }));
                GlobalPara.HisCfg.his.Add(str);
            }
            catch(Exception ex)
            {
                //Util.FileTool.SaveFailLog(ex.Message);
            }            
        }

        private int _SendTxtHisSelIndex = 0;
        public int SendTxtHisSelIndex
        {
            get => _SendTxtHisSelIndex;
            set
            {
                _SendTxtHisSelIndex = value;
                this.RaisePropertyChanged("SendTxtHisSelIndex");
            }
        }

        public void AddHisToSendText()
        {
            SendTxt = SendTxtHistory[SendTxtHisSelIndex];
        }

        private bool _SendCmdIsEnable = false;

        public bool SendCmdIsEnable
        {
            get => _SendCmdIsEnable;
            set
            {
                _SendCmdIsEnable = value;
                this.RaisePropertyChanged("SendCmdIsEnable");
            }
        }
        private string _SerialPortSettings = "";
        public string SerialPortSettings
        {
            get => _SerialPortSettings;
            set
            {
                _SerialPortSettings = value;
                this.RaisePropertyChanged("SerialPortSettings");
            }
        }

        

        #endregion

        #region Log

        private List<string> _LogList = new List<string>();

        private async Task<bool> SaveLogAsync(string strlog)
        {
            bool bret = false;
            if (LogPara.EnableWriteBuf)
            {
                long len = 0;
                long listlen = _LogList.Count;
                if (listlen > 0)
                {
                    
                    if(SendPara.IsHex)
                    {
                        for (int i = 0; i < listlen; i++)
                        {
                            len +=_LogList[i].Length/2;
                        }
                    }
                    else
                    {
                        for (int i = 0; i < listlen; i++)
                        {
                            len += System.Text.Encoding.Default.GetBytes(_LogList[i]).Length;
                        }
                    }
                    //len = System.Runtime.InteropServices.Marshal.SizeOf(_LogList);
                }
                if (len >= LogPara.BufSize)
                {
                    string strtmp = "";
                    for (int i = 0; i < listlen; i++)
                    {
                        //SaveLogBaseAsync(_LogList[i]); 
                        strtmp += _LogList[i]+Environment.NewLine;
                    }
                    await SaveLogBaseAsync(strtmp);
                    _LogList.Clear();
                }
                else
                {
                    _LogList.Add(strlog);
                }
            }
            else
            {
                //SaveLogBaseAsync(strlog);
                await SaveLogBaseAsync(strlog);
            }

            return bret;
        }

        private async Task<bool> SaveLogBaseAsync(string strlog)
        {
            bool bret = false;
            try
            {
                if (!LogPara.SaveLogMsg) return true;

                System.IO.FileInfo fileInfo = null;
                try
                {
                    fileInfo = new System.IO.FileInfo(LogPara.FileName);
                }
                catch (Exception ex)
                {
                    Util.FileTool.SaveFailLog("LogErr_"+ex.Message);
                    return false;
                }
                
                if (fileInfo != null && fileInfo.Exists)
                {
                    //System.Diagnostics.FileVersionInfo info = System.Diagnostics.FileVersionInfo.GetVersionInfo(LogPara.FileName);
                    float filsizeMB = fileInfo.Length/1024.0f;
                    if (filsizeMB >= LogPara.MaxFileSize)
                    {                       
                        LogPara.FileName = Path.Combine(GlobalPara.LogFolder,  DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".txt");
                    }
                }

                bret = Util.FileTool.SaveDebugLog(LogPara.FileName, strlog);
            }
            catch (Exception ex)
            {
                Util.FileTool.SaveFailLog(ex.Message);
                ComPortState = "Save Log Err";
                ComPortStateColor = GlobalPara.RedBrush;
            }
            return bret;
        }

        private void PrepareSerialLogFile(string portName)
        {
            if (!AutoSaveLogEnabled || string.IsNullOrWhiteSpace(portName))
            {
                return;
            }

            try
            {
                if (!Directory.Exists(GlobalPara.LogFolder))
                {
                    Directory.CreateDirectory(GlobalPara.LogFolder);
                }

                _LogList.Clear();
                string portId = ExtractPortIdentifier(portName);
                string timestamp = DateTime.Now.ToString("yyMMdd_HHmm");
                string fileName = $"serial{portId}_{timestamp}.log";
                LogPara.FileName = Path.Combine(GlobalPara.LogFolder, fileName);
            }
            catch (Exception ex)
            {
                Util.FileTool.SaveFailLog(ex.Message);
            }
        }

        private string ExtractPortIdentifier(string portName)
        {
            var matches = Regex.Matches(portName ?? string.Empty, @"\d+");
            if (matches.Count > 0)
            {
                return matches[matches.Count - 1].Value;
            }

            return (portName ?? "unknown").Replace(":", "").Replace("\\", "").Replace("/", "").Replace(" ", "");
        }

        private void FlushLogBufferIfNeeded()
        {
            if (!LogPara.EnableWriteBuf || _LogList.Count == 0 || !LogPara.SaveLogMsg)
            {
                return;
            }

            string strtmp = string.Join(Environment.NewLine, _LogList);
            _LogList.Clear();
            _ = SaveLogBaseAsync(strtmp);
        }

        private void SaveLogAs()
        {
            try
            {
                if (!Directory.Exists(GlobalPara.LogFolder))
                {
                    Directory.CreateDirectory(GlobalPara.LogFolder);
                }

                string defaultName = $"serial_log_{DateTime.Now:yyyyMMddHHmmss}.log";
                string defaultPath = Path.Combine(GlobalPara.LogFolder, defaultName);

                using (var dialog = new WinForms.SaveFileDialog())
                {
                    dialog.Filter = "Log Files|*.log|All Files|*.*";
                    dialog.FileName = defaultName;
                    dialog.InitialDirectory = GlobalPara.LogFolder;
                    if (dialog.ShowDialog() == WinForms.DialogResult.OK)
                    {
                        FlushLogBufferIfNeeded();

                        string sourceFile = LogPara.FileName;
                        if (!string.IsNullOrWhiteSpace(sourceFile) && File.Exists(sourceFile))
                        {
                            File.Copy(sourceFile, dialog.FileName, true);
                        }
                        else if (_LogList.Count > 0)
                        {
                            File.WriteAllText(dialog.FileName, string.Join(Environment.NewLine, _LogList));
                        }
                        else
                        {
                            File.WriteAllText(dialog.FileName, string.Empty);
                        }

                        MessageBox.Show("日志已保存", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        #endregion

        #region TCP 
        private TCPPara _TcpPara = new TCPPara();
        
        public TCPPara TcpPara
        {
            get { return _TcpPara; }
            set
            {
                _TcpPara = value;
                RaisePropertyChanged("TcpPara");
            }
        }
        #region TCP Client
        private ClientAsync _TcpClient;
        private void _TcpClient_Completed(System.Net.Sockets.TcpClient client, EnSocketAction arg2)
        {
            try
            {
                IPEndPoint iep = client.Client.RemoteEndPoint as IPEndPoint;
                string key = string.Format("{0}:{1}", iep.Address.ToString(), iep.Port);
                switch (arg2)
                {
                    case EnSocketAction.Connect:
                        ComPortState = client.Client.RemoteEndPoint.ToString() + " Connected";
                        ComPortStateColor = GlobalPara.GreenBrush;
                        IsStartCan = false;
                        IsStopCan = true;
                        IsPauseCan = true;
                        SendCmdIsEnable = true;
                        PauseBtnBackColor = GlobalPara.TransparentBrush;
                        break;
                    case EnSocketAction.SendMsg:
                        Console.WriteLine("{0}：向{1}发送了一条消息", DateTime.Now, key);                    
                        break;
                    case EnSocketAction.Close:
                        Console.WriteLine("服务端连接关闭");
                        ComPortState = client.Client.RemoteEndPoint.ToString() + " Closed";
                        ComPortStateColor = GlobalPara.RedBrush;
                        IsStartCan = true;
                        IsStopCan = false;
                        IsPauseCan = false;
                        SendCmdIsEnable = false;
                        PauseBtnBackColor = GlobalPara.TransparentBrush;
                        break;
                    default:
                        break;
                }
            }
            catch (Exception ex)
            {
                //Console.WriteLine(ex.Message);
                Util.FileTool.SaveFailLog(ex.ToString());
            }
        }
        private void _TcpClient_Received(TcpClient client, byte[] msg)
        {
            try
            {
                if (!_IsPauseCan) return;
                Thread.Sleep(10);              
                string receivestr = "";
                if (ReceivePara.IsHex)
                {
                    receivestr = DataConvertUtility.BytesToHexStrWithSpace(msg);
                }
                else
                {
                    receivestr = ReceivePara.TextEncoding.GetString(msg);
                }
                AddDataToChart(receivestr);
                if (ReceivePara.AutoFeed)
                {
                    receivestr += "\r\n";
                }
                if (ReceivePara.DisplayTime)
                {
                    receivestr = DateTime.Now.ToString(ReceivePara.TimeFormat) + receivestr;
                }
                
                receivestr = "[REC]" + receivestr;
                ShowMsgToMain(receivestr);               
                _ReceivedBytesNum += msg.Length;
                ReceiveBytesStr = "Rx: " + _ReceivedBytesNum + " Bytes";
                
                if (LogPara.SaveLogMsg)
                {
                    SaveLogAsync(receivestr);
                }
            }
            catch(Exception ex)
            {
                ComPortState = client.Client.RemoteEndPoint.ToString() + " Recive Process Error!";
                ComPortStateColor = GlobalPara.RedBrush;
                Util.FileTool.SaveFailLog(ex.ToString());
            }
        }
        #endregion

        #region TCP Server
        private ServerAsync _TcpServer;

        private ObservableCollection<TcpClient> _TcpClientLst;
        /// <summary>
        /// TCP服务器接收到的客户端集合
        /// </summary>
        public ObservableCollection<TcpClient> TcpClientLst
        {
            get { return _TcpClientLst; }
            set { _TcpClientLst = value;
                RaisePropertyChanged("TcpClientLst");
            }
        }
        /// <summary>
        /// TCPServer 的客户端返回的信息
        /// </summary>
        /// <param name="client"></param>
        /// <param name="msg"></param>
        private void _TcpServer_Received(TcpClient client, byte[] msg)
        {
            _TcpClient_Received(client, msg);
        }

        private void _TcpServer_Completed(TcpClient client, EnSocketAction arg2)
        {
            try
            {
                IPEndPoint iep = client.Client.RemoteEndPoint as IPEndPoint;
                string key = string.Format("{0}:{1}", iep.Address.ToString(), iep.Port);
                switch (arg2)
                {
                    case EnSocketAction.Connect:
                        App.Current.Dispatcher.BeginInvoke(new Action(() => {
                            if (TcpPara.TcpClients.Contains(key))
                            {
                                TcpPara.TcpClients.Remove(key);
                            }
                            TcpPara.TcpClients.Add(key);
                            ComPortState = key + " Connected";
                            if (!IsStartCan)
                            {
                                ComPortStateColor = GlobalPara.GreenBrush;
                                IsStartCan = false;
                                IsStopCan = true;
                                IsPauseCan = true;
                                SendCmdIsEnable = true;
                                PauseBtnBackColor = GlobalPara.TransparentBrush;
                            }
                        }));
                        break;
                    case EnSocketAction.SendMsg:
                       // Console.WriteLine("{0}：向{1}发送了一条消息", DateTime.Now, key);

                        break;
                    case EnSocketAction.Close:
                        App.Current.Dispatcher.BeginInvoke(new Action(() => {
                            if (TcpPara.TcpClients.Contains(key))
                            {
                                TcpPara.TcpClients.Remove(key);
                                ComPortState = key + " Closed";
                            }                        
                            if (TcpPara.TcpClients.Count == 0)
                            {
                                ComPortState = "No Clients";
                                ComPortStateColor = GlobalPara.RedBrush; 
                                SendCmdIsEnable = false;
                                PauseBtnBackColor = GlobalPara.TransparentBrush;
                            }
                            }));
                        break;
                    default:
                        break;
                }
            }
            catch (Exception ex)
            {
                // Console.WriteLine(ex.Message);
                Util.FileTool.SaveFailLog(ex.ToString());
            }
        }

        #endregion

        #endregion

        #region 曲线显示
        /// <summary>
        /// 是否正在显示表格
        /// </summary>
        private bool IsShowChart { get; set; } = false;

        ChartWindow _chartWindow = null;

        public DelegateCommand ShowChartParasCmd { get; private set; }
        private void ShowChartParas(object para)
        {
            //if(!GlobalPara.IsShowChart)
            //{
                ChartSet cs = new ChartSet();
                cs.ShowDialog();              
            //}
        }
        public DelegateCommand ShowChartShowCmd { get; private set; }
        private void ShowChartShow(object para)
        {
            if (!GlobalPara.IsShowChart)
            {
                _chartWindow = new ChartWindow(GlobalPara.ChartParas);               
                _chartWindow.Show();
            }
        }
        /// <summary>
        /// 添加数据到表格
        /// </summary>
        /// <param name="value"></param>
        private void AddDataToChart(string retstr)
        {
            if(GlobalPara.IsShowChart)
            {
                double value = 0;
                try
                {
                    
                    if(ReceivePara.IsHex)
                    {
                        byte[] btret=DataConvertUtility.HexStringToByte(retstr);
                        byte[] bval;
                        switch(GlobalPara.ChartParas.HexDataType)
                        {
                            case ChartParas.DataType.UShort:
                                bval = new byte[2];
                                switch (GlobalPara.ChartParas.HexByteOrder)
                                {
                                    case ChartParas.ByteOrder.B12:                                        
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex+1];                                        
                                        break;
                                    case ChartParas.ByteOrder.B21:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex+1];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        break;
                                }
                                value = BitConverter.ToUInt16(bval, 0);
                                break;
                            case ChartParas.DataType.UInt:
                                bval = new byte[4];
                                switch (GlobalPara.ChartParas.HexByteOrder)
                                {
                                    case ChartParas.ByteOrder.B1234:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        bval[2] = btret[GlobalPara.ChartParas.HexStartIndex+2];
                                        bval[3] = btret[GlobalPara.ChartParas.HexStartIndex + 3];
                                        break;
                                    case ChartParas.ByteOrder.B3412:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex + 2];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex+3];
                                        bval[2] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        bval[3] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        break;
                                    case ChartParas.ByteOrder.B4321:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex + 3];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex + 2];
                                        bval[2] = btret[GlobalPara.ChartParas.HexStartIndex+1];
                                        bval[3] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        break;
                                }
                                value=BitConverter.ToUInt32(bval,0);
                                break;
                            case ChartParas.DataType.Short:
                                bval = new byte[2];
                                switch (GlobalPara.ChartParas.HexByteOrder)
                                {
                                    case ChartParas.ByteOrder.B12:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        break;
                                    case ChartParas.ByteOrder.B21:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        break;
                                }
                                value = BitConverter.ToInt16(bval, 0);
                                break;
                            case ChartParas.DataType.Int:
                                bval = new byte[4];
                                switch (GlobalPara.ChartParas.HexByteOrder)
                                {
                                    case ChartParas.ByteOrder.B1234:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        bval[2] = btret[GlobalPara.ChartParas.HexStartIndex + 2];
                                        bval[3] = btret[GlobalPara.ChartParas.HexStartIndex + 3];
                                        break;
                                    case ChartParas.ByteOrder.B3412:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex + 2];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex + 3];
                                        bval[2] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        bval[3] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        break;
                                    case ChartParas.ByteOrder.B4321:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex + 3];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex + 2];
                                        bval[2] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        bval[3] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        break;
                                }
                                value = BitConverter.ToInt32(bval, 0);
                                break;
                            case ChartParas.DataType.Float:
                                bval = new byte[4];
                                switch (GlobalPara.ChartParas.HexByteOrder)
                                {
                                    case ChartParas.ByteOrder.B1234:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        bval[2] = btret[GlobalPara.ChartParas.HexStartIndex + 2];
                                        bval[3] = btret[GlobalPara.ChartParas.HexStartIndex + 3];
                                        break;
                                    case ChartParas.ByteOrder.B3412:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex + 2];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex + 3];
                                        bval[2] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        bval[3] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        break;
                                    case ChartParas.ByteOrder.B4321:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex + 3];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex + 2];
                                        bval[2] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        bval[3] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        break;
                                }
                                value = BitConverter.ToSingle(bval, 0);
                                break;
                            default:
                                bval = new byte[2];
                                switch (GlobalPara.ChartParas.HexByteOrder)
                                {
                                    case ChartParas.ByteOrder.B12:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        break;
                                    case ChartParas.ByteOrder.B21:
                                        bval[0] = btret[GlobalPara.ChartParas.HexStartIndex + 1];
                                        bval[1] = btret[GlobalPara.ChartParas.HexStartIndex];
                                        break;
                                }
                                value = BitConverter.ToUInt16(bval, 0);
                                break;
                        }
                    }
                    else
                    {
                        string str = retstr.Substring(GlobalPara.ChartParas.AsciiStartIndex, GlobalPara.ChartParas.AsciiLen);
                        value = double.Parse(str);
                    }

                    _chartWindow.viewModel.AddData(value);
                }
                catch
                {
                    ComPortState = "添加数据到表格失败";
                }
            }
        }


        #endregion

        #region modbus
        public DelegateCommand ShowIOTToolCmd { get; private set; }
        private void ShowIOTTool(object para)
        {
            IoTClientDeskTop.MainWindow window = new IoTClientDeskTop.MainWindow();
            window.ShowDialog();
        }
        public DelegateCommand ShowModbusCmd { get; private set; }
        private void ShowModbus(object para)
        {
            try
            {
                string temp = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\BYSerial\\";
                if (!Directory.Exists(temp))
                    Directory.CreateDirectory(temp);

                string f = temp + "Modbusl.dll";
                if (!File.Exists(f))
                    File.WriteAllBytes(f, Resources.Modbusl_dll);
                f = temp + "modsim32.cfg";
                if (!File.Exists(f))
                    File.WriteAllBytes(f, Resources.modsim32_cfg);
                f = temp + "ModSim32.cnt";
                if (!File.Exists(f))
                    File.WriteAllBytes(f, Resources.ModSim32_cnt);
                f = temp + "ModSim32.exe";
                if (!File.Exists(f))
                    File.WriteAllBytes(f, Resources.ModSim32_exe);
                f = temp + "MODSIM32.GID";
                if (!File.Exists(f))
                    File.WriteAllBytes(f, Resources.MODSIM32_GID);
                f = temp + "MODSIM32.HLP";
                if (!File.Exists(f))
                    File.WriteAllBytes(f, Resources.MODSIM32_HLP);
                f = temp + "ModSim32.ini";
                if (!File.Exists(f))
                    File.WriteAllText(f, Resources.ModSim32_ini);
                f = temp + "ModSim32.tlb";
                if (!File.Exists(f))
                    File.WriteAllBytes(f, Resources.ModSim32_tlb);
                f = temp + "ModSimEx.frm";
                if (!File.Exists(f))
                    File.WriteAllText(f, Resources.ModSimEx_frm);
                f = temp + "ModSimEx.vbp";
                if (!File.Exists(f))
                    File.WriteAllBytes(f, Resources.ModSimEx_vbp);
                f = temp + "ModSimEx.vbw";
                if (!File.Exists(f))
                    File.WriteAllBytes(f, Resources.ModSimEx_vbw);
                f = temp + "Modsim.lpu";
                if (!File.Exists(f))
                    File.WriteAllBytes(f, Resources.Modsim_lpu);
                f = temp + "ms32comm.cfg";
                if (!File.Exists(f))
                    File.WriteAllBytes(f, Resources.ms32comm_cfg);

                f = temp + "ModSim32.exe";
                Process.Start(f);
            }
            catch(Exception ex)
            {
                MessageBox.Show("启动modbus失败");
            }
        }
        #endregion

        #region 快捷命令列表
        private bool _IsLooping = false;
        
        private bool _FastCmdIsEnable = true;
        public bool FastCmdIsEnable
        {
            get { return _FastCmdIsEnable; }
            set
            {
                _FastCmdIsEnable = value;
                RaisePropertyChanged("FastCmdIsEnable");
            }
        }

        private bool _FastSendCmdIsEnable = false;
        public bool FastSendCmdIsEnable
        {
            get { return _FastSendCmdIsEnable; }
            set
            {
                _FastSendCmdIsEnable = value;
                RaisePropertyChanged("FastSendCmdIsEnable");
            }
        }        
        /// <summary>
        /// 快捷命令发送
        /// </summary>
        /// <param name="cmdstring"></param>
        /// <returns></returns>
        public bool SendCommandByFast(string cmdstring)
        {
            try
            {
                if(!FastSendCmdIsEnable)
                {
                   // MessageBox.Show("请先打开串口","提示",MessageBoxButton.OK,MessageBoxImage.Warning);
                    return false;
                }
                string txtsend = "";
                if (SendPara.IsHex)
                {
                    //如果是HEX,清空命令中的空格
                    string[] txts = cmdstring.Split(' ');
                    string txtssend = "";
                    for (int i = 0; i < txts.Length; i++)
                    {
                        txtssend += txts[i];
                    }
                    txtsend = txtssend;
                }
                else
                {
                    //如果是ASCII,对空格不做处理
                    txtsend = cmdstring;
                }
                if (SendPara.FormatSend)
                {
                    if (SendPara.FormatCRNL)
                    {
                        txtsend += "\r\n";
                    }
                    else if (SendPara.FormatNLCR)
                    {
                        txtsend += "\n\r";
                    }
                    else if (SendPara.FormatNewLine)
                    {
                        txtsend += "\r";
                    }
                    else if (SendPara.FormatCarReturn)
                    {
                        txtsend += "\n";
                    }
                }
                int byteNum = 0;

                if (SendPara.IsHex)
                {
                    txtsend = txtsend.Replace(" ", "").ToUpper(); //20220616格式化字符串
                    if (txtsend.Length % 2 != 0)
                    {
                        MessageBox.Show("输入字符长度为奇数，命令不可发送；请检查命令是否有错！\r\n一个字节至少2个字符，不足请补零", "错误提示");
                        return false;
                    }
                    byte[] btSend = new byte[1];
                    if (SendPara.AutoCRC)
                    {
                        string strcrc = CommonCheck.CheckCRC16Modbus(txtsend);
                        txtsend += strcrc; //20220616增加修复 自动计算CRC错误，并且不显示完整的命令字符串
                        btSend = Util.DataConvertUtility.HexStringToByte(txtsend);
                    }
                    else
                    {
                        btSend = Util.DataConvertUtility.HexStringToByte(txtsend);
                    }
                    if (IsSerialTest == Visibility.Visible)
                    {
                        _serialPort.Write(btSend, 0, btSend.Length);
                    }
                    else
                    {
                        if (TcpPara.bIsTcpClient)
                        {
                            _TcpClient.SendAsync(btSend);
                        }
                        else if (TcpPara.bIsTcpServer)
                        {
                            _TcpServer.SendAsync(TcpPara.TcpClients[TcpPara.SvrClientsIndex], btSend);
                        }
                    }
                    byteNum = btSend.Length;

                }
                else 
                {
                    if (IsSerialTest == Visibility.Visible)
                    {
                        //使串口支持中文。
                        byte[] bts = SendPara.TextEncoding.GetBytes(txtsend);
                        _serialPort.Write(bts, 0, bts.Length);
                        //_serialPort.Write(txtsend);
                    }
                    else
                    {
                        if (TcpPara.bIsTcpClient)
                        {
                            _TcpClient.SendAsync(txtsend, SendPara.TextEncoding);
                        }
                        else if (TcpPara.bIsTcpServer)
                        {
                            _TcpServer.SendAsync(TcpPara.TcpClients[TcpPara.SvrClientsIndex], txtsend, SendPara.TextEncoding);
                        }
                    }
                    byteNum = SendPara.TextEncoding.GetBytes(txtsend).Length; // Encoding.UTF8.GetBytes(txtsend).Length;
                    
                }

                if (ReceivePara.DisplaySend)
                {
                    if (ReceivePara.AutoFeed)
                    {
                        if (SendPara.IsHex)
                        {
                            txtsend = txtsend.Replace(" ", "");
                            txtsend = Util.DataConvertUtility.InsertFormat(txtsend, 2, " ") + "\r\n";
                        }
                        else
                        {
                            txtsend += "\r\n";
                        }
                    }
                    if (ReceivePara.DisplayTime)
                    {
                        txtsend = DateTime.Now.ToString(ReceivePara.TimeFormat) + txtsend;
                    }
                    txtsend = "[SEND]" + txtsend;
                    ShowMsgToMain(txtsend);                  
                    if (LogPara.SaveLogMsg)
                    {
                        SaveLogAsync(txtsend);
                    }
                }
                _SendedBytesNum += byteNum;
                SendBytesStr = "Tx: " + _SendedBytesNum + " Bytes";                
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            return false;
        }
        /// <summary>
        /// 显示到主界面，限制1000行信息
        /// </summary>
        /// <param name="pg"></param>
        private void ShowMsgToMain(string msg)
        {
            lock (_messageLock)
            {
                _messageBuffer.Add(msg);
                if (_messageBuffer.Count > MaxDisplayMessages)
                {
                    _messageBuffer.RemoveAt(0);
                }
            }

            var keyword = ReceivePara?.FilterText ?? string.Empty;
            bool isFilterActive = ReceivePara != null && ReceivePara.FilterEnabled && keyword.Length > 0;
            if (isFilterActive && msg.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) < 0)
            {
                return;
            }

            AppendProductionLog(msg);

            App.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                if (_ReciveFlowDoc == null) return;
                if (_ReciveFlowDoc.Blocks.Count >= MaxDisplayMessages)
                {
                    _ReciveFlowDoc.Blocks.Remove(_ReciveFlowDoc.Blocks.FirstBlock);
                }
                _ReciveFlowDoc.Blocks.Add(CreateParagraph(msg));
            }));
        }

        private void ReceivePara_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(ReceivePara.FilterEnabled) || e.PropertyName == nameof(ReceivePara.FilterText))
            {
                RefreshFilteredMessages();
            }
        }

        private void RefreshFilteredMessages()
        {
            if (_ReciveFlowDoc == null) return;

            List<string> snapshot;
            lock (_messageLock)
            {
                snapshot = _messageBuffer.ToList();
            }

            var keyword = ReceivePara?.FilterText ?? string.Empty;
            bool isFilterActive = ReceivePara != null && ReceivePara.FilterEnabled && keyword.Length > 0;

            App.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                _ReciveFlowDoc.Blocks.Clear();
                foreach (var message in snapshot)
                {
                    if (isFilterActive && message.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) < 0)
                    {
                        continue;
                    }
                    _ReciveFlowDoc.Blocks.Add(CreateParagraph(message));
                }
            }));
        }

        private Paragraph CreateParagraph(string msg)
        {
            Paragraph pg = new Paragraph
            {
                Margin = new Thickness(1),
                Padding = new Thickness(0)
            };
            pg.Inlines.Add(new Run(msg));
            if (DisplayPara != null && DisplayPara.FormatDisColor)
            {
                pg.Foreground = DisplayPara.SendColor;
            }
            return pg;
        }
        private ObservableCollection<FastCmdModel> _FastCmdList = new ObservableCollection<FastCmdModel>();
        public ObservableCollection<FastCmdModel> FastCmdList
        {
            get => _FastCmdList;
            set
            {
                _FastCmdList = value;
                RaisePropertyChanged("FastCmdList");
            }
        }
        private int _FastCmdSelIndex = 0;
        public int FastCmdSelIndex
        {
            get => _FastCmdSelIndex;
            set
            {
                _FastCmdSelIndex = value;
                RaisePropertyChanged("FastCmdSelIndex");
                _FastCmdList[value].RowID = value;
            }
        }
        private bool _FastCmdLstEnable = true;
        public bool FastCmdLstEnable
        {
            get => _FastCmdLstEnable;
            set
            {
                _FastCmdLstEnable = value;
                RaisePropertyChanged("FastCmdLstEnable");
            }
        }

        private FastCmdModel _CurSelectedFastCmdModel;
        public FastCmdModel CurSelectedFastCmdModel
        {
            get => _CurSelectedFastCmdModel;
            set
            {
                _CurSelectedFastCmdModel = value;
                RaisePropertyChanged("CurSelectedFastCmdModel");
            }
        }

        private bool _FastCmdIsCycle;
        /// <summary>
        /// 快捷命令循环发送
        /// </summary>
        public bool FastCmdIsCycle
        {
            get { return _FastCmdIsCycle; }
            set
            {
                _FastCmdIsCycle = value;
                RaisePropertyChanged("FastCmdIsCycle");
            }
        }

        private bool _FastCmdIsStartSend;
        /// <summary>
        /// 快捷发送复选框是否选中
        /// </summary>
        public bool FastCmdIsStartSend
        {
            get { return _FastCmdIsStartSend; }
            set
            {
                _FastCmdIsStartSend = value;
                RaisePropertyChanged("FastCmdIsStartSend");
                if (value)
                {
                    //开始发送快捷命令
                    if (FastCmdIsCycle)
                    {
                        SendCmdIsEnable = false;
                        _IsLooping = true;
                        SendFastCmdLoop();
                    }
                    else
                    {
                        SendFastCmdOnce();
                    }
                }
            }
        }
        private void SendFastCmdOnce()
        {
            for (int i = 0; i < _FastCmdList.Count; i++)
            {
                if (!FastCmdIsStartSend) break;
                Thread.Sleep(_FastCmdList[i].DelayTime);
                if (_FastCmdList[i].IsSelected)
                {
                    _FastCmdList[i].SendFastCmd(null);
                }
            }
        }

        private void SendFastCmdLoop()
        {
            Task t = new Task(() => {

                while (FastCmdIsCycle)
                {
                    if (!FastCmdIsStartSend || !_IsLooping ||!IsStopCan)
                    {
                        break;
                    }
                    for (int i = 0; i < _FastCmdList.Count; i++)
                    {
                        if (!FastCmdIsStartSend || !_IsLooping || !IsStopCan)
                        {
                            break;
                        }                        
                        if (_FastCmdList[i].IsSelected)
                        {
                            _FastCmdList[i].SendFastCmd(null);
                            Thread.Sleep(_FastCmdList[i].DelayTime); //20240510 修正命令行被选中时再等待。
                        }
                    }
                }
                SendCmdIsEnable = true;

            });
            t.ContinueWith(t1 =>
            {
                GC.Collect();
            });  //解决线程占用
            t.Start();
        }

        public DelegateCommand FastCmdDelCmd { get; private set; }
        private void FastCmdDel(object para)
        {
            try
            {
                _FastCmdList.RemoveAt(FastCmdSelIndex);
                for (int i = 0; i < _FastCmdList.Count; i++)
                {
                    _FastCmdList[i].CmdNum = i + 1;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        public DelegateCommand FastCmdAddCmd { get; private set; }
        private void FastCmdAdd(object para)
        {
            try
            {
                FastCmdModel model = new FastCmdModel();
                model.SendCmd = SendCommandByFast;
                model.CmdNum = _FastCmdList.Count + 1;
                model.RowID = _FastCmdList.Count;
                model.DelayTime = 50;
                FastCmdSet fcs = new FastCmdSet(model);
                bool ret = (bool)fcs.ShowDialog();
                if (ret)
                {
                    FastCmdList.Add(model);
                    FastCmdSelIndex = FastCmdSelIndex;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }



        #endregion

        #region ModeSwitch

        public DelegateCommand SwitchToDebugModeCmd { get; private set; }
        public DelegateCommand SwitchToProductionModeCmd { get; private set; }
        public DelegateCommand SwitchToBurnModeCmd { get; private set; }
        public DelegateCommand StartReadCommand { get; private set; }
        public DelegateCommand StartBurnCommand { get; private set; }
        public DelegateCommand StartFirmwareFlashCommand { get; private set; }
        public DelegateCommand ClearFirmwareBurnLogCommand { get; private set; }
        public DelegateCommand SelectBurnFolderCommand { get; private set; }
        public DelegateCommand OpenBurnFolderCommand { get; private set; }

        private enum AppMode
        {
            Debug,
            Production,
            Burn
        }

        private AppMode _currentMode = AppMode.Debug;
        private string _portNameBeforeModeChange = string.Empty;

        public bool IsProductionMode
        {
            get => _currentMode == AppMode.Production;
            set
            {
                if (value)
                {
                    SwitchToProductionMode(null);
                }
                else if (_currentMode == AppMode.Production)
                {
                    SwitchToDebugMode(null);
                }
            }
        }

        public bool IsDebugMode
        {
            get => _currentMode == AppMode.Debug;
            set
            {
                if (value)
                {
                    SwitchToDebugMode(null);
                }
                else if (_currentMode == AppMode.Debug)
                {
                    SwitchToProductionMode(null);
                }
            }
        }

        public bool IsBurnMode => _currentMode == AppMode.Burn;

        public Visibility DebugModeVisibility => IsDebugMode ? Visibility.Visible : Visibility.Collapsed;

        public Visibility ProductionModeVisibility => IsProductionMode ? Visibility.Visible : Visibility.Collapsed;

        public Visibility BurnModeVisibility => IsBurnMode ? Visibility.Visible : Visibility.Collapsed;

        private string _productionReadSn = string.Empty;
        public string ProductionReadSn
        {
            get => _productionReadSn;
            set
            {
                if (_productionReadSn != value)
                {
                    _productionReadSn = value;
                    RaisePropertyChanged();
                }
            }
        }

        private string _productionReadBluetoothName = string.Empty;
        public string ProductionReadBluetoothName
        {
            get => _productionReadBluetoothName;
            set
            {
                if (_productionReadBluetoothName != value)
                {
                    _productionReadBluetoothName = value;
                    RaisePropertyChanged();
                }
            }
        }

        private string _productionReadBluetoothAddress = string.Empty;
        public string ProductionReadBluetoothAddress
        {
            get => _productionReadBluetoothAddress;
            set
            {
                string formatted = FormatMacForDisplay(value);
                if (_productionReadBluetoothAddress != formatted)
                {
                    _productionReadBluetoothAddress = formatted;
                    RaisePropertyChanged();
                }
            }
        }

        private string _productionReadBleName = string.Empty;
        public string ProductionReadBleName
        {
            get => _productionReadBleName;
            set
            {
                if (_productionReadBleName != value)
                {
                    _productionReadBleName = value;
                    RaisePropertyChanged();
                }
            }
        }

        private string _productionReadBleAddress = string.Empty;
        public string ProductionReadBleAddress
        {
            get => _productionReadBleAddress;
            set
            {
                string formatted = FormatMacForDisplay(value);
                if (_productionReadBleAddress != formatted)
                {
                    _productionReadBleAddress = formatted;
                    RaisePropertyChanged();
                }
            }
        }

        private string _productionReceiveLog = string.Empty;
        public string ProductionReceiveLog
        {
            get => _productionReceiveLog;
            set
            {
                if (_productionReceiveLog != value)
                {
                    _productionReceiveLog = value;
                    RaisePropertyChanged();
                }
            }
        }

        private string _burnStatusMessage = string.Empty;
        public string BurnStatusMessage
        {
            get => _burnStatusMessage;
            set
            {
                if (_burnStatusMessage != value)
                {
                    _burnStatusMessage = value;
                    RaisePropertyChanged();
                }
            }
        }

        private Brush _burnStatusBrush = Brushes.Transparent;
        public Brush BurnStatusBrush
        {
            get => _burnStatusBrush;
            set
            {
                if (_burnStatusBrush != value)
                {
                    _burnStatusBrush = value;
                    RaisePropertyChanged();
                }
            }
        }

        #region FirmwareBurnMode

        private readonly List<BurnFirmwareOptionDefinition> _burnFirmwareOptionDefinitions = new List<BurnFirmwareOptionDefinition>();
        public ObservableCollection<BurnFirmwareOption> BurnFirmwareOptions { get; } = new ObservableCollection<BurnFirmwareOption>();

        private BurnFirmwareOption _selectedBurnFirmwareOption;
        public BurnFirmwareOption SelectedBurnFirmwareOption
        {
            get => _selectedBurnFirmwareOption;
            set
            {
                if (_selectedBurnFirmwareOption != value)
                {
                    _selectedBurnFirmwareOption = value;
                    RaisePropertyChanged();
                    RaisePropertyChanged(nameof(BurnModeDescription));
                }
            }
        }

        public string BurnModeDescription =>
            SelectedBurnFirmwareOption?.Description ??
            GetLangString("burnModeSelectOptionPlaceholder", "Select an image to flash");

        private string _burnDirectoryPath = string.Empty;
        public string BurnDirectoryPath
        {
            get => _burnDirectoryPath;
            set
            {
                string normalized = (value ?? string.Empty).Trim();
                if (_burnDirectoryPath != normalized)
                {
                    _burnDirectoryPath = normalized;
                    RaisePropertyChanged();
                }
            }
        }

        private string _burnModeLog = string.Empty;
        public string BurnModeLog
        {
            get => _burnModeLog;
            set
            {
                if (_burnModeLog != value)
                {
                    _burnModeLog = value;
                    RaisePropertyChanged();
                }
            }
        }

        private double _burnProgressValue;
        public double BurnProgressValue
        {
            get => _burnProgressValue;
            set
            {
                if (Math.Abs(_burnProgressValue - value) > 0.001)
                {
                    _burnProgressValue = value;
                    RaisePropertyChanged();
                }
            }
        }

        private string _burnProgressText = string.Empty;
        public string BurnProgressText
        {
            get => _burnProgressText;
            set
            {
                if (_burnProgressText != value)
                {
                    _burnProgressText = value;
                    RaisePropertyChanged();
                }
            }
        }


        private string _burnModeStatusText = string.Empty;
        public string BurnModeStatusText
        {
            get => _burnModeStatusText;
            set
            {
                if (_burnModeStatusText != value)
                {
                    _burnModeStatusText = value;
                    RaisePropertyChanged();
                }
            }
        }

        private Brush _burnModeStatusBrush = Brushes.Gray;
        public Brush BurnModeStatusBrush
        {
            get => _burnModeStatusBrush;
            set
            {
                if (_burnModeStatusBrush != value)
                {
                    _burnModeStatusBrush = value;
                    RaisePropertyChanged();
                }
            }
        }

        private Brush _burnProgressBrush = Brushes.SteelBlue;
        public Brush BurnProgressBrush
        {
            get => _burnProgressBrush;
            set
            {
                if (_burnProgressBrush != value)
                {
                    _burnProgressBrush = value;
                    RaisePropertyChanged();
                }
            }
        }

        private bool _burnProgressIndeterminate;
        public bool BurnProgressIndeterminate
        {
            get => _burnProgressIndeterminate;
            set
            {
                if (_burnProgressIndeterminate != value)
                {
                    _burnProgressIndeterminate = value;
                    RaisePropertyChanged();
                }
            }
        }

        private Visibility _burnProgressVisibility = Visibility.Visible;
        public Visibility BurnProgressVisibility
        {
            get => _burnProgressVisibility;
            set
            {
                if (_burnProgressVisibility != value)
                {
                    _burnProgressVisibility = value;
                    RaisePropertyChanged();
                }
            }
        }

        private bool _burnModeStartEnabled = true;
        public bool BurnModeStartEnabled
        {
            get => _burnModeStartEnabled;
            set
            {
                if (_burnModeStartEnabled != value)
                {
                    _burnModeStartEnabled = value;
                    RaisePropertyChanged();
                }
            }
        }

        private readonly HashSet<string> _firmwareProgressStepIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        private readonly List<string> _firmwareProgressOrder = new List<string>();
        private readonly HashSet<string> _firmwareProgressCompletedIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        private int _firmwareProgressTotalSteps;
        private bool _isFirmwareBurning;
        private readonly List<string> _lastFirmwareFilesUsed = new List<string>();
        private readonly HashSet<string> _lastFirmwareFilesSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        private bool _autoSaveLogEnabled = true;

        private class BurnFirmwareOptionDefinition
        {
            public BurnFirmwareOptionDefinition(BurnFirmwareOptionType optionType, string titleKey, string titleFallback, string descriptionKey, string descriptionFallback)
            {
                OptionType = optionType;
                TitleResourceKey = titleKey;
                TitleFallback = titleFallback;
                DescriptionResourceKey = descriptionKey;
                DescriptionFallback = descriptionFallback;
            }

            public BurnFirmwareOptionType OptionType { get; }
            public string TitleResourceKey { get; }
            public string TitleFallback { get; }
            public string DescriptionResourceKey { get; }
            public string DescriptionFallback { get; }
        }

        #endregion

        private string _productionSn = string.Empty;
        public string ProductionSn
        {
            get => _productionSn;
            set
            {
                if (_productionSn != value)
                {
                    _productionSn = value;
                    RaisePropertyChanged();
                }
            }
        }

        private string _productionBluetoothName = string.Empty;
        public string ProductionBluetoothName
        {
            get => _productionBluetoothName;
            set
            {
                if (_productionBluetoothName != value)
                {
                    _productionBluetoothName = value;
                    RaisePropertyChanged();
                }
            }
        }

        private string _productionBluetoothAddress = string.Empty;
        public string ProductionBluetoothAddress
        {
            get => _productionBluetoothAddress;
            set
            {
                string formatted = FormatMacForDisplay(value);
                if (_productionBluetoothAddress != formatted)
                {
                    _productionBluetoothAddress = formatted;
                    RaisePropertyChanged();
                }
            }
        }

        private string _productionBleName = string.Empty;
        public string ProductionBleName
        {
            get => _productionBleName;
            set
            {
                if (_productionBleName != value)
                {
                    _productionBleName = value;
                    RaisePropertyChanged();
                }
            }
        }

        private string _productionBleAddress = string.Empty;
        public string ProductionBleAddress
        {
            get => _productionBleAddress;
            set
            {
                string formatted = FormatMacForDisplay(value);
                if (_productionBleAddress != formatted)
                {
                    _productionBleAddress = formatted;
                    RaisePropertyChanged();
                }
            }
        }

        private int _productionSnStepIndex = 0;
        public int ProductionSnStepIndex
        {
            get => _productionSnStepIndex;
            set
            {
                if (_productionSnStepIndex != value)
                {
                    _productionSnStepIndex = value;
                    RaisePropertyChanged();
                }
            }
        }

        private int _productionBluetoothAddressStepIndex = 0;
        public int ProductionBluetoothAddressStepIndex
        {
            get => _productionBluetoothAddressStepIndex;
            set
            {
                if (_productionBluetoothAddressStepIndex != value)
                {
                    _productionBluetoothAddressStepIndex = value;
                    RaisePropertyChanged();
                }
            }
        }

        private int _productionBleAddressStepIndex = 0;
        public int ProductionBleAddressStepIndex
        {
            get => _productionBleAddressStepIndex;
            set
            {
                if (_productionBleAddressStepIndex != value)
                {
                    _productionBleAddressStepIndex = value;
                    RaisePropertyChanged();
                }
            }
        }

        private void SwitchToDebugMode(object para)
        {
            if (IsDebugMode)
            {
                return;
            }

            if (IsProductionMode)
            {
                ClearProductionReceiveLog();
            }

            UpdateSerialPortList();
            if (!string.IsNullOrEmpty(_portNameBeforeModeChange) && SerialPortList != null)
            {
                int restoredIndex = SerialPortList.IndexOf(_portNameBeforeModeChange);
                if (restoredIndex >= 0)
                {
                    PortNameIndex = restoredIndex;
                }
            }
            IsSerialTest = Visibility.Visible;
            SetMode(AppMode.Debug);
        }

        private void SwitchToProductionMode(object para)
        {
            if (IsProductionMode)
            {
                return;
            }

            RememberCurrentPortSelection();
            SetMode(AppMode.Production);
            ClearProductionReceiveLog();
            IsSerialTest = Visibility.Visible;
            EnsurePhysicalSerialPortSelected();
        }

        private void SwitchToBurnMode(object para)
        {
            if (IsBurnMode)
            {
                return;
            }

            RememberCurrentPortSelection();
            if (IsProductionMode)
            {
                ClearProductionReceiveLog();
            }
            IsSerialTest = Visibility.Visible;
            EnsurePhysicalSerialPortSelected();
            SetMode(AppMode.Burn);
        }

        private void RememberCurrentPortSelection()
        {
            if (SerialPortList != null && PortNameIndex >= 0 && PortNameIndex < SerialPortList.Count)
            {
                _portNameBeforeModeChange = SerialPortList[PortNameIndex];
            }
            else
            {
                _portNameBeforeModeChange = string.Empty;
            }
        }

        private void EnsurePhysicalSerialPortSelected()
        {
            if (SerialPortList == null || SerialPortList.Count == 0)
            {
                return;
            }

            int tcpIndex = SerialPortList.IndexOf("TCP");
            if (tcpIndex >= 0 && PortNameIndex == tcpIndex)
            {
                int fallbackIndex = tcpIndex > 0 ? 0 : (SerialPortList.Count > 1 ? 1 : 0);
                if (fallbackIndex >= 0 && fallbackIndex < SerialPortList.Count)
                {
                    PortNameIndex = fallbackIndex;
                }
            }
        }

        private void SetMode(AppMode mode)
        {
            if (_currentMode == mode)
            {
                return;
            }

            _currentMode = mode;
            RaisePropertyChanged(nameof(IsProductionMode));
            RaisePropertyChanged(nameof(IsDebugMode));
            RaisePropertyChanged(nameof(IsBurnMode));
            RaisePropertyChanged(nameof(DebugModeVisibility));
            RaisePropertyChanged(nameof(ProductionModeVisibility));
            RaisePropertyChanged(nameof(BurnModeVisibility));
        }

        private void OnStartRead(object para)
        {
            if (!EnsureSerialReadyForBurn())
            {
                return;
            }

            ClearReadFields(null);
            SendBurnCommand("besfactory devinfo 0");
        }
                private async Task OnStartBurnAsync()
        {
            if (!EnsureSerialReadyForBurn())
            {
                return;
            }

            if (!TryBuildBurnCommands(out List<string> commands, out string errorMessage))
            {
                MessageBox.Show(errorMessage, GetLangString("dialogWarning", "Warning"), MessageBoxButton.OK, MessageBoxImage.Warning);
                lock (_burnStatusLock)
                {
                    _pendingBurnTargets.Clear();
                    _burnVerificationFailed = false;
                }
                UpdateBurnStatus(errorMessage, GlobalPara.RedBrush);
                return;
            }

            BurnButtonEnabled = false;
            try
            {
                InitializeBurnVerificationTargets();
                await Task.Delay(200);

                foreach (string command in commands)
                {
                    if (!SendBurnCommand(command))
                    {
                        lock (_burnStatusLock)
                        {
                            _pendingBurnTargets.Clear();
                            _burnVerificationFailed = true;
                        }
                        UpdateBurnStatus(GetLangString("prodStatusBurnSendFailed", "Failed to send burn command."), GlobalPara.RedBrush);
                        return;
                    }
                    await Task.Delay(100);
                }

                await Task.Delay(200);
                ApplyAddressAutoStepAfterBurn();
            }
            finally
            {
                BurnButtonEnabled = true;
            }
        }

        private bool EnsureSerialReadyForBurn()
        {
            if (IsSerialTest != Visibility.Visible)
            {
                MessageBox.Show("生产模式烧写仅支持串口，请选择有效的串口。", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
                return false;
            }

            if (_serialPort == null || !_serialPort.IsOpen)
            {
                MessageBox.Show("请先打开串口再执行烧写操作。", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
                return false;
            }

            return true;
        }

        private bool TryBuildBurnCommands(out List<string> commands, out string errorMessage)
        {
            commands = new List<string>();
            errorMessage = string.Empty;

            string sn = (ProductionSn ?? string.Empty).Trim();
            if (!Regex.IsMatch(sn, @"^[A-Za-z0-9]{20}$"))
            {
                errorMessage = "SN 必须为18位数字。";
                return false;
            }

            string btName = (ProductionBluetoothName ?? string.Empty).Trim();
            if (string.IsNullOrWhiteSpace(btName))
            {
                errorMessage = "蓝牙名称不能为空。";
                return false;
            }

            if (!TryNormalizeAddress(ProductionBluetoothAddress, out string btAddress, out errorMessage))
            {
                errorMessage = string.IsNullOrEmpty(errorMessage) ? "蓝牙地址格式错误，需为12位十六进制字符。" : errorMessage;
                return false;
            }

            string bleName = (ProductionBleName ?? string.Empty).Trim();
            if (string.IsNullOrWhiteSpace(bleName))
            {
                errorMessage = "BLE 名称不能为空。";
                return false;
            }

            if (!TryNormalizeAddress(ProductionBleAddress, out string bleAddress, out errorMessage))
            {
                errorMessage = string.IsNullOrEmpty(errorMessage) ? "BLE 地址格式错误，需为12位十六进制字符。" : errorMessage;
                return false;
            }

            ProductionSn = sn;
            ProductionBluetoothName = btName;
            ProductionBluetoothAddress = btAddress;
            ProductionBleName = bleName;
            ProductionBleAddress = bleAddress;

            commands.Add($"besfactory sn {sn}");
            commands.Add($"besfactory btname {QuoteNameForCommand(btName)}");
            commands.Add($"besfactory btmac {FormatMacForCommand(btAddress)}");
            commands.Add($"besfactory blename {QuoteNameForCommand(bleName)}");
            commands.Add($"besfactory blemac {FormatMacForCommand(bleAddress)}");

            return true;
        }

        private static string QuoteNameForCommand(string name)
        {
            string cleaned = (name ?? string.Empty).Trim();
            if (cleaned.IndexOf(' ') < 0 && cleaned.IndexOf('\t') < 0)
            {
                return cleaned;
            }

            cleaned = cleaned.Replace("\"", "\\\"");
            return $"\"{cleaned}\"";
        }

        private static bool TryNormalizeAddress(string input, out string normalized, out string errorMessage)
        {
            normalized = string.Empty;
            errorMessage = string.Empty;

            if (string.IsNullOrWhiteSpace(input))
            {
                errorMessage = "地址不能为空。";
                return false;
            }

            string candidate = input.Replace(" ", string.Empty)
                                    .Replace(":", string.Empty)
                                    .Replace("-", string.Empty)
                                    .Trim()
                                    .ToUpperInvariant();

            if (!Regex.IsMatch(candidate, @"^[0-9A-F]{12}$"))
            {
                errorMessage = "地址需要为12位十六进制字符。";
                return false;
            }

            normalized = candidate;
            return true;
        }

        private bool SendBurnCommand(string command)
        {
            try
            {
                if (_serialPort == null || !_serialPort.IsOpen)
                {
                    MessageBox.Show("串口未打开，无法发送烧写指令。", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
                    return false;
                }

                string commandWithEnding = command.EndsWith("\r\n", StringComparison.Ordinal) ? command : command + "\r\n";
                Encoding encoding = SendPara?.TextEncoding ?? Encoding.ASCII;
                byte[] bytes = encoding.GetBytes(commandWithEnding);
                _serialPort.Write(bytes, 0, bytes.Length);

                if (ReceivePara?.DisplaySend == true)
                {
                    string logText = command;
                    if (ReceivePara.AutoFeed)
                    {
                        logText += "\r\n";
                    }
                    if (ReceivePara.DisplayTime)
                    {
                        logText = DateTime.Now.ToString(ReceivePara.TimeFormat) + logText;
                    }
                    logText = "[SEND]" + logText;
                    ShowMsgToMain(logText);
                    if (LogPara?.SaveLogMsg == true)
                    {
                        SaveLogAsync(logText);
                    }
                }

                _SendedBytesNum += bytes.Length;
                SendBytesStr = "Tx: " + _SendedBytesNum + " Bytes";
                AddSendHistory(command);
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("烧写指令发送失败：" + ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                return false;
            }
        }

        private void ApplyAddressAutoStepAfterBurn()
        {
            ProductionSn = ApplySnStep(ProductionSn, ProductionSnStepIndex);
            ProductionBluetoothAddress = ApplyAddressStep(ProductionBluetoothAddress, ProductionBluetoothAddressStepIndex);
            ProductionBleAddress = ApplyAddressStep(ProductionBleAddress, ProductionBleAddressStepIndex);
        }

        private string ApplyAddressStep(string address, int stepIndex)
        {
            if (string.IsNullOrWhiteSpace(address))
            {
                return address;
            }

            string cleaned = address.Trim().ToUpperInvariant();
            if (!Regex.IsMatch(cleaned, @"^[0-9A-F]+$"))
            {
                return address;
            }

            try
            {
                ulong value = Convert.ToUInt64(cleaned, 16);
                switch (stepIndex)
                {
                    case 0:
                        return cleaned;
                    case 1:
                        value += 1;
                        break;
                    case 2:
                        if (value == 0)
                        {
                            return cleaned;
                        }
                        value -= 1;
                        break;
                    default:
                        return cleaned;
                }

                string formatted = value.ToString("X").PadLeft(cleaned.Length, '0');
                return FormatMacForDisplay(formatted);
            }
            catch
            {
                return address;
            }
        }

        private static string FormatMacForCommand(string address)
        {
            if (string.IsNullOrEmpty(address))
            {
                return address;
            }

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < address.Length; i += 2)
            {
                if (i > 0)
                {
                    sb.Append(":");
                }
                int len = Math.Min(2, address.Length - i);
                sb.Append(address.Substring(i, len));
            }
            return sb.ToString();
        }

        private static string FormatMacForDisplay(string address)
        {
            if (string.IsNullOrEmpty(address))
            {
                return address;
            }

            string cleaned = address.Replace(":", string.Empty)
                                    .Replace("-", string.Empty)
                                    .Trim();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < cleaned.Length; i += 2)
            {
                if (i > 0)
                {
                    sb.Append(":");
                }
                int len = Math.Min(2, cleaned.Length - i);
                sb.Append(cleaned.Substring(i, len));
            }
            return sb.ToString().ToUpperInvariant();
        }

        private void InitializeBurnVerificationTargets()
        {
            lock (_burnStatusLock)
            {
                _pendingBurnTargets.Clear();
                _burnVerificationFailed = false;
                AddBurnTarget("sn", NormalizeSn(ProductionSn));
                AddBurnTarget("btname", NormalizeName(ProductionBluetoothName));
                AddBurnTarget("btmac", FormatMacForDisplay(ProductionBluetoothAddress));
                AddBurnTarget("blename", NormalizeName(ProductionBleName));
                AddBurnTarget("blemac", FormatMacForDisplay(ProductionBleAddress));
            }
            UpdateBurnStatus("烧写中...", Brushes.DarkOrange);
        }

        private void AddBurnTarget(string key, string value)
        {
            if (!string.IsNullOrWhiteSpace(value))
            {
                _pendingBurnTargets[key] = value.Trim();
            }
        }

        private static string NormalizeSn(string sn) => (sn ?? string.Empty).Trim();
        private static string NormalizeName(string name) => (name ?? string.Empty).Trim().Trim('"');

        private void ProcessBurnFeedback(string payload)
        {
            if (string.IsNullOrWhiteSpace(payload) || _pendingBurnTargets.Count == 0 || _burnVerificationFailed)
            {
                return;
            }

            string[] lines = payload.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < lines.Length; i++)
            {
                string line = lines[i].Trim();
                if (line.Length == 0)
                {
                    continue;
                }

                if (line.IndexOf("factory_section_set_serial_num", StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    ValidateBurnValue("sn", NormalizeSn(ExtractNewValue(line)));
                }
                else if (line.IndexOf("factory_section_set_bt_name", StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    ValidateBurnValue("btname", NormalizeName(ExtractNewValue(line)));
                }
                else if (line.IndexOf("factory_section_set_ble_name", StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    ValidateBurnValue("blename", NormalizeName(ExtractNewValue(line)));
                }
                else if (line.IndexOf("Update bt addr as", StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    string addressLine = (i + 1) < lines.Length ? lines[i + 1] : string.Empty;
                    string formatted = FormatAddressFromHexLine(addressLine);
                    if (!string.IsNullOrEmpty(formatted))
                    {
                        ValidateBurnValue("btmac", formatted);
                    }
                    i++;
                }
                else if (line.IndexOf("Update ble addr as", StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    string addressLine = (i + 1) < lines.Length ? lines[i + 1] : string.Empty;
                    string formatted = FormatAddressFromHexLine(addressLine);
                    if (!string.IsNullOrEmpty(formatted))
                    {
                        ValidateBurnValue("blemac", formatted);
                    }
                    i++;
                }
            }

        }

        private static string ExtractNewValue(string line)
        {
            var match = Regex.Match(line, @"\[NEW\d*\]\s*(.+)$", RegexOptions.IgnoreCase);
            return match.Success ? match.Groups[1].Value.Trim() : string.Empty;
        }

        private static string FormatAddressFromHexLine(string line)
        {
            if (string.IsNullOrWhiteSpace(line))
            {
                return string.Empty;
            }
            var parts = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length == 0)
            {
                return string.Empty;
            }
            var tokens = new List<string>();
            foreach (var token in parts)
            {
                string cleaned = token.Trim().TrimEnd(',');
                if (Regex.IsMatch(cleaned, @"^[0-9A-Fa-f]{2}$"))
                {
                    tokens.Add(cleaned.ToUpperInvariant());
                }
            }
            if (tokens.Count == 0)
            {
                return string.Empty;
            }
            return string.Join(":", tokens);
        }

        private string GetLangString(string key, string fallback)
        {
            object res = Application.Current?.TryFindResource(key);
            if (res is string s && !string.IsNullOrEmpty(s))
            {
                return s;
            }
            return fallback;
        }

        private void ValidateBurnValue(string key, string actualValue)
        {
            string statusMessage = null;
            Brush statusBrush = null;

            lock (_burnStatusLock)
            {
                if (string.IsNullOrWhiteSpace(actualValue) || !_pendingBurnTargets.TryGetValue(key, out var expected) || _burnVerificationFailed)
                {
                    return;
                }

                if (string.Equals(expected, actualValue, StringComparison.OrdinalIgnoreCase))
                {
                    _pendingBurnTargets.Remove(key);
                    if (_pendingBurnTargets.Count == 0)
                    {
                        statusMessage = "烧写成功";
                        statusBrush = GlobalPara.GreenBrush;
                    }
                }
                else
                {
                _pendingBurnTargets.Clear();
                _burnVerificationFailed = true;
                statusMessage = $"{GetBurnDisplayName(key)}校验失败";
                statusBrush = GlobalPara.RedBrush;
                }
            }

            if (statusMessage != null)
            {
                UpdateBurnStatus(statusMessage, statusBrush);
            }
        }

        private string GetBurnDisplayName(string key)
        {
            switch (key.ToLowerInvariant())
            {
                case "sn": return "SN";
                case "btname": return "蓝牙名称";
                case "btmac": return "蓝牙地址";
                case "blename": return "BLE名称";
                case "blemac": return "BLE地址";
                default: return key;
            }
        }

        private void UpdateBurnStatus(string message, Brush brush)
        {
            void Apply()
            {
                BurnStatusMessage = message;
                BurnStatusBrush = brush;
            }

            if (Application.Current != null)
            {
                Application.Current.Dispatcher.BeginInvoke(new Action(Apply));
            }
            else
            {
                Apply();
            }
        }

        private void ProcessDevInfoPayload(string payload)
        {
            if (string.IsNullOrWhiteSpace(payload))
            {
                return;
            }

            string[] lines = payload.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string rawLine in lines)
            {
                string line = rawLine.Trim();
                if (!line.StartsWith("DEVINFO#", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                string content = line.Substring("DEVINFO#".Length);
                int idx = content.IndexOf('=');
                if (idx <= 0 || idx >= content.Length - 1)
                {
                    continue;
                }
                string key = content.Substring(0, idx).Trim().ToLowerInvariant();
                string value = content.Substring(idx + 1).Trim();

                switch (key)
                {
                    case "sn":
                        ProductionReadSn = value;
                        break;
                    case "btname":
                        ProductionReadBluetoothName = value;
                        break;
                    case "btmac":
                        ProductionReadBluetoothAddress = value;
                        break;
                    case "blename":
                        ProductionReadBleName = value;
                        break;
                    case "blemac":
                        ProductionReadBleAddress = value;
                        break;
                }
            }
        }

        #region ExternalFirmwareFlash

        private void InitializeBurnFirmwareOptions()
        {
            if (_burnFirmwareOptionDefinitions.Count > 0)
            {
                return;
            }

            _burnFirmwareOptionDefinitions.Add(new BurnFirmwareOptionDefinition(BurnFirmwareOptionType.All, "burnOptionAll", "Full Flash", "burnOptionAllDesc", "Erase chip and flash every image"));
            _burnFirmwareOptionDefinitions.Add(new BurnFirmwareOptionDefinition(BurnFirmwareOptionType.Rtos, "burnOptionRtos", "RTOS Only", "burnOptionRtosDesc", "Flash M55 firmware and Bluetooth co-processor"));
            _burnFirmwareOptionDefinitions.Add(new BurnFirmwareOptionDefinition(BurnFirmwareOptionType.Bth, "burnOptionBth", "BTH (M33) Only", "burnOptionBthDesc", "Flash the M33 BTH core image"));
            _burnFirmwareOptionDefinitions.Add(new BurnFirmwareOptionDefinition(BurnFirmwareOptionType.Hifi4, "burnOptionHifi", "HIFI4 Only", "burnOptionHifiDesc", "Flash the HIFI4 image"));
            _burnFirmwareOptionDefinitions.Add(new BurnFirmwareOptionDefinition(BurnFirmwareOptionType.Norflash, "burnOptionNor", "NOR File System", "burnOptionNorDesc", "Flash the NOR flash file system"));
            _burnFirmwareOptionDefinitions.Add(new BurnFirmwareOptionDefinition(BurnFirmwareOptionType.NoApp, "burnOptionNoApp", "NoApp Test Image", "burnOptionNoAppDesc", "Erase chip and flash the NoApp firmware"));
            _burnFirmwareOptionDefinitions.Add(new BurnFirmwareOptionDefinition(BurnFirmwareOptionType.Platform, "burnOptionPlatform", "Platform Test", "burnOptionPlatformDesc", "Erase chip and flash platform test images"));
            _burnFirmwareOptionDefinitions.Add(new BurnFirmwareOptionDefinition(BurnFirmwareOptionType.Factory, "burnOptionFactory", "Factory Info", "burnOptionFactoryDesc", "Flash factory section and write BT info"));
            _burnFirmwareOptionDefinitions.Add(new BurnFirmwareOptionDefinition(BurnFirmwareOptionType.Emmc, "burnOptionEmmc", "eMMC File System", "burnOptionEmmcDesc", "Flash the eMMC FS image"));
        }

        private void RefreshBurnFirmwareOptionTexts()
        {
            if (_burnFirmwareOptionDefinitions.Count == 0)
            {
                return;
            }

            BurnFirmwareOptionType? previousSelection = SelectedBurnFirmwareOption?.OptionType;
            BurnFirmwareOptions.Clear();
            foreach (var definition in _burnFirmwareOptionDefinitions)
            {
                string title = GetLangString(definition.TitleResourceKey, definition.TitleFallback);
                string description = GetLangString(definition.DescriptionResourceKey, definition.DescriptionFallback);
                BurnFirmwareOptions.Add(new BurnFirmwareOption(definition.OptionType, title, description));
            }

            if (previousSelection.HasValue)
            {
                SelectedBurnFirmwareOption = BurnFirmwareOptions.FirstOrDefault(opt => opt.OptionType == previousSelection.Value) ?? BurnFirmwareOptions.FirstOrDefault();
            }
            else
            {
                SelectedBurnFirmwareOption = BurnFirmwareOptions.FirstOrDefault();
            }
            RaisePropertyChanged(nameof(BurnModeDescription));
        }

        private string DetectDefaultBurnDirectory()
        {
            string baseDir = AppDomain.CurrentDomain.BaseDirectory;
            string bundledDefault = Path.Combine(baseDir, BurnToolFolderName, "bin");
            var candidates = new[]
            {
                bundledDefault,
                Path.GetFullPath(Path.Combine(baseDir, @"..\BurnFW\bin")),
                Path.GetFullPath(Path.Combine(baseDir, @"..\..\BurnFW\bin")),
                Path.GetFullPath(Path.Combine(baseDir, @"..\..\..\BurnFW\bin")),
                Path.GetFullPath(Path.Combine(baseDir, @"..\..\..\..\BurnFW\bin")),
                Path.Combine(Environment.CurrentDirectory, BurnToolFolderName, "bin")
            };

            foreach (string candidate in candidates)
            {
                if (Directory.Exists(candidate))
                {
                    return candidate;
                }
            }

            return bundledDefault;
        }

        private string DetectDefaultBurnToolDirectory()
        {
            string baseDir = AppDomain.CurrentDomain.BaseDirectory;
            string bundledToolDir = Path.Combine(baseDir, BurnToolFolderName);
            if (Directory.Exists(bundledToolDir))
            {
                return bundledToolDir;
            }

            string currentDirTool = Path.Combine(Environment.CurrentDirectory, BurnToolFolderName);
            if (Directory.Exists(currentDirTool))
            {
                return currentDirTool;
            }

            return bundledToolDir;
        }

        private void ClearFirmwareBurnLog(object obj)
        {
            BurnModeLog = string.Empty;
        }

        private void SelectBurnFolder(object obj)
        {
            try
            {
                using (var dialog = new WinForms.FolderBrowserDialog())
                {
                    dialog.Description = GetLangString("burnModeFolderLabel", "Firmware Folder");
                    string initialPath = null;
                    if (Directory.Exists(BurnDirectoryPath))
                    {
                        initialPath = BurnDirectoryPath;
                    }
                    else
                    {
                        string detected = DetectDefaultBurnDirectory();
                        if (!string.IsNullOrEmpty(detected) && Directory.Exists(detected))
                        {
                            initialPath = detected;
                        }
                    }
                    if (string.IsNullOrEmpty(initialPath))
                    {
                        string baseDir = AppDomain.CurrentDomain.BaseDirectory;
                        if (Directory.Exists(baseDir))
                        {
                            initialPath = baseDir;
                        }
                        else if (Directory.Exists(Environment.CurrentDirectory))
                        {
                            initialPath = Environment.CurrentDirectory;
                        }
                    }
                    if (!string.IsNullOrEmpty(initialPath))
                    {
                        dialog.SelectedPath = initialPath;
                    }

                    if (dialog.ShowDialog() == WinForms.DialogResult.OK)
                    {
                        string selectedPath = dialog.SelectedPath;
                        string binCandidate = Path.Combine(selectedPath, "bin");
                        if (Directory.Exists(binCandidate))
                        {
                            BurnDirectoryPath = binCandidate;
                        }
                        else
                        {
                            BurnDirectoryPath = selectedPath;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, GetLangString("dialogWarning", "Warning"), MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private void OpenBurnFolder(object obj)
        {
            string folder = BurnDirectoryPath;
            if (string.IsNullOrWhiteSpace(folder) || !Directory.Exists(folder))
            {
                folder = DetectDefaultBurnDirectory();
            }

            if (string.IsNullOrWhiteSpace(folder) || !Directory.Exists(folder))
            {
                MessageBox.Show(GetLangString("burnModeDirMissing", "Flash folder not found"), GetLangString("dialogWarning", "Warning"), MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                Process.Start(new ProcessStartInfo
                {
                    FileName = folder,
                    UseShellExecute = true
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, GetLangString("dialogWarning", "Warning"), MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private async Task StartFirmwareFlashAsync()
        {
            if (_isFirmwareBurning)
            {
                return;
            }

            var option = SelectedBurnFirmwareOption;
            if (option == null)
            {
                string warning = GetLangString("burnModeSelectOptionWarning", "Select an image before flashing");
                MessageBox.Show(warning, GetLangString("dialogWarning", "Warning"), MessageBoxButton.OK, MessageBoxImage.Warning);
                UpdateFirmwareBurnStatus(warning, GlobalPara.RedBrush);
                return;
            }

            if (!TryBuildFirmwareFlashProcessInfo(option, out ProcessStartInfo startInfo, out string errorMessage))
            {
                if (!string.IsNullOrEmpty(errorMessage))
                {
                    MessageBox.Show(errorMessage, GetLangString("dialogWarning", "Warning"), MessageBoxButton.OK, MessageBoxImage.Warning);
                    UpdateFirmwareBurnStatus(errorMessage, GlobalPara.RedBrush);
                }
                return;
            }

            _isFirmwareBurning = true;
            BurnModeStartEnabled = false;
            UpdateFirmwareBurnStatus(GetLangString("burnModeStatusRunning", "Flashing..."), Brushes.DarkOrange);
            AppendBurnModeLog($"[{DateTime.Now:HH:mm:ss}] {startInfo.FileName} {startInfo.Arguments}");
            try
            {
                using (var process = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
                {
                    var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
                    process.OutputDataReceived += (s, e) =>
                    {
                        if (!string.IsNullOrEmpty(e.Data))
                        {
                            AppendBurnModeLog(e.Data);
                        }
                    };
                    process.ErrorDataReceived += (s, e) =>
                    {
                        if (!string.IsNullOrEmpty(e.Data))
                        {
                            AppendBurnModeLog(e.Data);
                        }
                    };
                    process.Exited += (s, e) => tcs.TrySetResult(process.ExitCode);

                    if (!process.Start())
                    {
                        UpdateFirmwareBurnStatus(GetLangString("burnModeStatusFailed", "Flash Failed"), GlobalPara.RedBrush);
                        SetBurnProgressFinalState(GlobalPara.RedBrush);
                        LogFirmwareFilesUsed();
                        return;
                    }
                    process.BeginOutputReadLine();
                    process.BeginErrorReadLine();

                    int exitCode = await tcs.Task.ConfigureAwait(false);
                    if (exitCode == 0)
                    {
                        CompleteFirmwareProgressTracking();
                        UpdateFirmwareBurnStatus(GetLangString("burnModeStatusSuccess", "Flash Completed"), GlobalPara.GreenBrush);
                        SetBurnProgressFinalState(GlobalPara.GreenBrush);
                    }
                    else
                    {
                        string failText = $"{GetLangString("burnModeStatusFailed", "Flash Failed")} (code {exitCode})";
                        UpdateFirmwareBurnStatus(failText, GlobalPara.RedBrush);
                        SetBurnProgressFinalState(GlobalPara.RedBrush);
                    }

                    LogFirmwareFilesUsed();
                }
            }
            catch (Exception ex)
            {
                UpdateFirmwareBurnStatus(GetLangString("burnModeStatusFailed", "Flash Failed"), GlobalPara.RedBrush);
                SetBurnProgressFinalState(GlobalPara.RedBrush);
                AppendBurnModeLog(ex.Message);
                LogFirmwareFilesUsed();
            }
            finally
            {
                _isFirmwareBurning = false;
                BurnModeStartEnabled = true;
            }
        }

        private bool TryBuildFirmwareFlashProcessInfo(BurnFirmwareOption option, out ProcessStartInfo startInfo, out string errorMessage)
        {
            startInfo = null;
            errorMessage = string.Empty;

            string firmwareDir = BurnDirectoryPath;
            if (string.IsNullOrWhiteSpace(firmwareDir) || !Directory.Exists(firmwareDir))
            {
                errorMessage = GetLangString("burnModeDirMissing", "Flash folder not found");
                return false;
            }

            string defaultToolDirectory = DetectDefaultBurnToolDirectory();
            var toolCandidates = new List<string>();
            if (!string.IsNullOrWhiteSpace(defaultToolDirectory) && Directory.Exists(defaultToolDirectory))
            {
                toolCandidates.Add(defaultToolDirectory);
            }

            string firmwareParent = Directory.GetParent(firmwareDir)?.FullName;
            if (!string.IsNullOrWhiteSpace(firmwareParent))
            {
                toolCandidates.Add(firmwareParent);
            }
            toolCandidates.Add(firmwareDir);

            string toolDirectory = null;
            string toolPath = null;
            foreach (string candidate in toolCandidates.Where(c => !string.IsNullOrWhiteSpace(c)))
            {
                string path = Path.Combine(candidate, DldToolExecutableName);
                if (File.Exists(path))
                {
                    toolDirectory = candidate;
                    toolPath = path;
                    break;
                }
            }

            if (string.IsNullOrEmpty(toolPath) || !File.Exists(toolPath))
            {
                errorMessage = GetLangString("burnModeToolMissing", "dldtool.exe not found");
                return false;
            }

            if (!TryGetSelectedPortNumber(out string portNumber, out errorMessage))
            {
                return false;
            }

            if (!TryBuildFirmwareFlashArguments(firmwareDir, portNumber, option.OptionType, out string arguments, out errorMessage))
            {
                return false;
            }

            startInfo = new ProcessStartInfo
            {
                FileName = toolPath,
                Arguments = arguments,
                WorkingDirectory = toolDirectory,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                CreateNoWindow = true
            };
            return true;
        }

        private bool TryGetSelectedPortNumber(out string portNumber, out string errorMessage)
        {
            portNumber = string.Empty;
            errorMessage = string.Empty;

            string selected = string.IsNullOrWhiteSpace(BurnPortText)
                ? (SerialPortList != null && PortNameIndex >= 0 && PortNameIndex < SerialPortList.Count
                    ? SerialPortList[PortNameIndex]
                    : string.Empty)
                : BurnPortText.Trim();

            if (string.IsNullOrWhiteSpace(selected) || string.Equals(selected, "TCP", StringComparison.OrdinalIgnoreCase))
            {
                errorMessage = GetLangString("burnModePortInvalid", "Select a valid COM port");
                return false;
            }

            bool exists = SerialPort.GetPortNames().Any(p => string.Equals(p, selected, StringComparison.OrdinalIgnoreCase));
            if (!exists)
            {
                errorMessage = $"{GetLangString("burnModePortInvalid", "Select a valid COM port")} ({selected})";
                return false;
            }

            Match match = Regex.Match(selected ?? string.Empty, @"\d+");
            if (!match.Success)
            {
                errorMessage = GetLangString("burnModePortInvalid", "Select a valid COM port");
                return false;
            }

            portNumber = match.Value;
            return true;
        }

        private bool TryBuildFirmwareFlashArguments(string firmwareDir, string portNumber, BurnFirmwareOptionType optionType, out string arguments, out string errorMessage)
        {
            ResetFirmwareProgressPlan(true);
            var args = new List<string> { "--reboot", portNumber };
            errorMessage = string.Empty;

            bool success = optionType switch
            {
                BurnFirmwareOptionType.All => BuildAllArguments(firmwareDir, args, out errorMessage),
                BurnFirmwareOptionType.Rtos => BuildRtosArguments(firmwareDir, args, out errorMessage),
                BurnFirmwareOptionType.Bth => BuildBthArguments(firmwareDir, args, out errorMessage),
                BurnFirmwareOptionType.Hifi4 => BuildHifiArguments(firmwareDir, args, out errorMessage),
                BurnFirmwareOptionType.Norflash => BuildNorflashArguments(firmwareDir, args, out errorMessage),
                BurnFirmwareOptionType.NoApp => BuildNoAppArguments(firmwareDir, args, out errorMessage),
                BurnFirmwareOptionType.Platform => BuildPlatformArguments(firmwareDir, args, out errorMessage),
                BurnFirmwareOptionType.Factory => BuildFactoryArguments(firmwareDir, args, out errorMessage),
                BurnFirmwareOptionType.Emmc => BuildEmmcArguments(firmwareDir, args, out errorMessage),
                _ => false
            };

            if (!success)
            {
                arguments = string.Empty;
                return false;
            }

            FinalizeFirmwareProgressPlan();
            arguments = string.Join(" ", args);
            return true;
        }

        private bool BuildAllArguments(string firmwareDir, List<string> args, out string errorMessage)
        {
            args.Add("--erase-chip");
            RegisterFirmwareProgressStep("ERASE_CHIP");
            if (!TryAddFileArgument(args, firmwareDir, ProgrammerDualFileName, out errorMessage))
            {
                return false;
            }
            AddProgramRate(args);
            if (!TryAddFileArgument(args, firmwareDir, RemapFileName, out errorMessage))
            {
                return false;
            }
            if (!TryAddBootInfo(args, firmwareDir, out errorMessage))
            {
                return false;
            }
            if (!TryAddOta(args, firmwareDir, out errorMessage))
            {
                return false;
            }
            if (!TryAddRtos(args, firmwareDir, RtosFileName, out errorMessage))
            {
                return false;
            }
            if (!TryAddM33C0(args, firmwareDir, out errorMessage))
            {
                return false;
            }
            if (!TryAddCpbth(args, firmwareDir, out errorMessage))
            {
                return false;
            }
            if (!TryAddHifi(args, firmwareDir, out errorMessage))
            {
                return false;
            }
            if (!TryAddFatfs(args, firmwareDir, out errorMessage))
            {
                return false;
            }
            if (!TryAddFlashPattern(args, firmwareDir, out errorMessage))
            {
                return false;
            }
            AddFactoryInfoTokens(args);
            return true;
        }

        private bool BuildRtosArguments(string firmwareDir, List<string> args, out string errorMessage)
        {
            if (!TryAddFileArgument(args, firmwareDir, ProgrammerDualFileName, out errorMessage))
            {
                return false;
            }
            AddProgramRate(args);
            if (!TryAddFileArgument(args, firmwareDir, RemapFileName, out errorMessage))
            {
                return false;
            }
            if (!TryAddRtos(args, firmwareDir, RtosFileName, out errorMessage))
            {
                return false;
            }
            return TryAddCpbth(args, firmwareDir, out errorMessage);
        }

        private bool BuildBthArguments(string firmwareDir, List<string> args, out string errorMessage)
        {
            if (!TryAddFileArgument(args, firmwareDir, ProgrammerDualFileName, out errorMessage))
            {
                return false;
            }
            AddProgramRate(args);
            if (!TryAddFileArgument(args, firmwareDir, RemapFileName, out errorMessage))
            {
                return false;
            }
            return TryAddM33C0(args, firmwareDir, out errorMessage);
        }

        private bool BuildHifiArguments(string firmwareDir, List<string> args, out string errorMessage)
        {
            if (!TryAddFileArgument(args, firmwareDir, ProgrammerDualFileName, out errorMessage))
            {
                return false;
            }
            AddProgramRate(args);
            if (!TryAddFileArgument(args, firmwareDir, RemapFileName, out errorMessage))
            {
                return false;
            }
            return TryAddHifi(args, firmwareDir, out errorMessage);
        }

        private bool BuildNorflashArguments(string firmwareDir, List<string> args, out string errorMessage)
        {
            if (!TryAddFileArgument(args, firmwareDir, ProgrammerDualFileName, out errorMessage))
            {
                return false;
            }
            AddProgramRate(args);
            if (!TryAddFileArgument(args, firmwareDir, RemapFileName, out errorMessage))
            {
                return false;
            }
            return TryAddFatfs(args, firmwareDir, out errorMessage);
        }

        private bool BuildNoAppArguments(string firmwareDir, List<string> args, out string errorMessage)
        {
            args.Add("--erase-chip");
            RegisterFirmwareProgressStep("ERASE_CHIP");
            if (!TryAddFileArgument(args, firmwareDir, ProgrammerDualFileName, out errorMessage))
            {
                return false;
            }
            AddProgramRate(args);
            if (!TryAddFileArgument(args, firmwareDir, RemapFileName, out errorMessage))
            {
                return false;
            }
            return TryAddRtos(args, firmwareDir, NoAppFileCandidates, out errorMessage);
        }

        private bool BuildPlatformArguments(string firmwareDir, List<string> args, out string errorMessage)
        {
            args.Add("--erase-chip");
            RegisterFirmwareProgressStep("ERASE_CHIP");
            if (!TryAddFileArgument(args, firmwareDir, ProgrammerDualFileName, out errorMessage))
            {
                return false;
            }
            AddProgramRate(args);
            if (!TryAddFileArgument(args, firmwareDir, RemapFileName, out errorMessage))
            {
                return false;
            }
            if (!TryAddRtos(args, firmwareDir, PlatformTestFileCandidates, out errorMessage))
            {
                return false;
            }
            if (!TryAddPlatformM55Image(args, firmwareDir, out errorMessage))
            {
                return false;
            }
            return TryAddM33C0(args, firmwareDir, out errorMessage);
        }

        private bool BuildFactoryArguments(string firmwareDir, List<string> args, out string errorMessage)
        {
            if (!TryAddFileArgument(args, firmwareDir, ProgrammerDualFileName, out errorMessage))
            {
                return false;
            }
            AddProgramRate(args);
            if (!TryAddFileArgument(args, firmwareDir, RemapFileName, out errorMessage))
            {
                return false;
            }
            if (!TryAddFactoryRtos(args, firmwareDir, out errorMessage))
            {
                return false;
            }
            AddFactoryInfoTokens(args);
            return true;
        }

        private bool BuildEmmcArguments(string firmwareDir, List<string> args, out string errorMessage)
        {
            if (!TryAddFileArgument(args, firmwareDir, ProgrammerEmmcFileName, out errorMessage))
            {
                return false;
            }
            AddProgramRate(args);
            args.Add("--addr");
            args.Add("0x3");
            return TryAddFileArgument(args, firmwareDir, EmmcFileName, out errorMessage);
        }

        private void AddProgramRate(List<string> args)
        {
            args.Add("--pgm-rate");
            args.Add("2000000");
        }

        private string ResolveFirmwareFilePath(string firmwareDir, string candidate)
        {
            if (string.IsNullOrWhiteSpace(candidate))
            {
                return string.Empty;
            }

            string normalized = candidate.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
            bool useBinDirectory = ShouldUseFirmwareBinDirectory(normalized);
            string searchDirectory = useBinDirectory
                ? EnsureBinDirectory(firmwareDir)
                : GetFirmwareRootDirectory(firmwareDir);

            string path = string.Empty;
            if (!string.IsNullOrWhiteSpace(searchDirectory))
            {
                path = Path.Combine(searchDirectory, normalized);
                if (File.Exists(path))
                {
                    return path;
                }
            }

            if (!useBinDirectory)
            {
                string defaultToolDir = DetectDefaultBurnToolDirectory();
                if (!string.IsNullOrWhiteSpace(defaultToolDir))
                {
                    string fallbackPath = Path.Combine(defaultToolDir, normalized);
                    if (File.Exists(fallbackPath))
                    {
                        return fallbackPath;
                    }
                }
            }
            else
            {
                string defaultBinDir = DetectDefaultBurnDirectory();
                if (!string.IsNullOrWhiteSpace(defaultBinDir))
                {
                    string fallbackBinPath = Path.Combine(defaultBinDir, normalized);
                    if (File.Exists(fallbackBinPath))
                    {
                        return fallbackBinPath;
                    }
                }
            }

            return path;
        }

        private string EnsureBinDirectory(string firmwareDir)
        {
            if (string.IsNullOrWhiteSpace(firmwareDir))
            {
                return string.Empty;
            }

            try
            {
                var dirInfo = new DirectoryInfo(firmwareDir);
                if (string.Equals(dirInfo.Name, "bin", StringComparison.OrdinalIgnoreCase))
                {
                    return dirInfo.FullName;
                }

                string binPath = Path.Combine(dirInfo.FullName, "bin");
                if (Directory.Exists(binPath))
                {
                    return binPath;
                }

                return firmwareDir;
            }
            catch
            {
                return firmwareDir;
            }
        }

        private string GetFirmwareRootDirectory(string firmwareDir)
        {
            if (string.IsNullOrWhiteSpace(firmwareDir))
            {
                return string.Empty;
            }

            try
            {
                var dirInfo = new DirectoryInfo(firmwareDir);
                if (string.Equals(dirInfo.Name, "bin", StringComparison.OrdinalIgnoreCase))
                {
                    return dirInfo.Parent?.FullName ?? firmwareDir;
                }

                return dirInfo.FullName;
            }
            catch
            {
                return firmwareDir;
            }
        }

        private bool ShouldUseFirmwareBinDirectory(string relativePath)
        {
            string fileName = Path.GetFileName(relativePath);
            if (string.IsNullOrEmpty(fileName))
            {
                return true;
            }

            return FirmwareBinFileNames.Contains(fileName);
        }

        private bool TryAddFileArgument(List<string> args, string firmwareDir, string relativePath, out string errorMessage)
        {
            return TryAddFileArgument(args, firmwareDir, new[] { relativePath }, out errorMessage);
        }

        private bool TryAddFileArgument(List<string> args, string firmwareDir, IEnumerable<string> candidates, out string errorMessage)
        {
            foreach (string candidate in candidates)
            {
                string path = ResolveFirmwareFilePath(firmwareDir, candidate);
                if (!string.IsNullOrEmpty(path) && File.Exists(path))
                {
                    args.Add(QuotePath(path));
                    RegisterFileProgressStep(path);
                    errorMessage = string.Empty;
                    return true;
                }
            }

            string missing = candidates.LastOrDefault() ?? string.Empty;
            errorMessage = string.Format(GetLangString("burnModeFileMissing", "Missing file: {0}"), missing);
            return false;
        }

        private string QuotePath(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                return string.Empty;
            }
            if (path.IndexOf(" ", StringComparison.Ordinal) >= 0)
            {
                return "\"" + path + "\"";
            }
            return path;
        }

        private bool TryAddBootInfo(List<string> args, string firmwareDir, out string errorMessage)
        {
            args.Add("--set-dual-chip");
            args.Add("1");
            args.Add("--addr");
            args.Add("0x30020000");
            return TryAddFileArgument(args, firmwareDir, BootInfoFileName, out errorMessage);
        }

        private bool TryAddOta(List<string> args, string firmwareDir, out string errorMessage)
        {
            args.Add("--set-dual-chip");
            args.Add("1");
            args.Add("--addr");
            args.Add("0x30040000");
            args.Add("-M");
            return TryAddFileArgument(args, firmwareDir, OtaFileName, out errorMessage);
        }

        private bool TryAddRtos(List<string> args, string firmwareDir, string relativePath, out string errorMessage)
        {
            args.Add("--set-dual-chip");
            args.Add("1");
            args.Add("-M");
            args.Add("--appbinary");
            return TryAddFileArgument(args, firmwareDir, relativePath, out errorMessage);
        }

        private bool TryAddRtos(List<string> args, string firmwareDir, IEnumerable<string> candidates, out string errorMessage)
        {
            args.Add("--set-dual-chip");
            args.Add("1");
            args.Add("-M");
            args.Add("--appbinary");
            return TryAddFileArgument(args, firmwareDir, candidates, out errorMessage);
        }

        private bool TryAddFactoryRtos(List<string> args, string firmwareDir, out string errorMessage)
        {
            args.Add("--set-dual-chip");
            args.Add("1");
            args.Add("--appbinary");
            return TryAddFileArgument(args, firmwareDir, RtosFileName, out errorMessage);
        }

        private bool TryAddM33C0(List<string> args, string firmwareDir, out string errorMessage)
        {
            args.Add("--addr");
            args.Add("0x28000000");
            args.Add("-M");
            return TryAddFileArgument(args, firmwareDir, M33C0FileName, out errorMessage);
        }

        private bool TryAddCpbth(List<string> args, string firmwareDir, out string errorMessage)
        {
            args.Add("--set-dual-chip");
            args.Add("1");
            args.Add("--addr");
            args.Add("0x30680000");
            args.Add("-M");
            return TryAddFileArgument(args, firmwareDir, CpbthFileName, out errorMessage);
        }

        private bool TryAddHifi(List<string> args, string firmwareDir, out string errorMessage)
        {
            args.Add("--set-dual-chip");
            args.Add("1");
            args.Add("--addr");
            args.Add("0x30A80000");
            args.Add("-M");
            return TryAddFileArgument(args, firmwareDir, HifiFileName, out errorMessage);
        }

        private bool TryAddFatfs(List<string> args, string firmwareDir, out string errorMessage)
        {
            args.Add("--set-dual-chip");
            args.Add("1");
            args.Add("--addr");
            args.Add("0x30C80000");
            return TryAddFileArgument(args, firmwareDir, FatfsFileName, out errorMessage);
        }

        private bool TryAddFlashPattern(List<string> args, string firmwareDir, out string errorMessage)
        {
            args.Add("--addr");
            args.Add("0x283F6000");
            return TryAddFileArgument(args, firmwareDir, FlashPatternFileName, out errorMessage);
        }

        private bool TryAddPlatformM55Image(List<string> args, string firmwareDir, out string errorMessage)
        {
            args.Add("--set-dual-chip");
            args.Add("1");
            args.Add("--addr");
            args.Add("0x30680000");
            args.Add("-M");
            return TryAddFileArgument(args, firmwareDir, PlatformM55FileCandidates, out errorMessage);
        }

        private void AddFactoryInfoTokens(List<string> args)
        {
            args.AddRange(FactoryInfoTokens);
            RegisterFirmwareProgressStep("Factory");
        }

        private void AppendBurnModeLog(string message)
        {
            if (string.IsNullOrWhiteSpace(message))
            {
                return;
            }

            ProcessFirmwareProgressMessage(message);

            Application.Current.Dispatcher?.BeginInvoke(new Action(() =>
            {
                if (string.IsNullOrEmpty(BurnModeLog))
                {
                    BurnModeLog = message;
                }
                else
                {
                    BurnModeLog += Environment.NewLine + message;
                }
            }));
        }

        private void UpdateFirmwareBurnStatus(string message, Brush brush)
        {
            BurnModeStatusText = message;
            BurnModeStatusBrush = brush ?? Brushes.Gray;
        }

        private void ProcessFirmwareProgressMessage(string rawMessage)
        {
            if (_firmwareProgressTotalSteps <= 0 || string.IsNullOrWhiteSpace(rawMessage))
            {
                return;
            }

            string[] segments = rawMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string segment in segments)
            {
                if (segment.IndexOf("main_loop/------ Burn File consumes", StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    Application.Current.Dispatcher?.BeginInvoke(new Action(MarkNextFirmwareProgressStepFromQueue));
                    continue;
                }

                if (segment.IndexOf("main_loop/------ Burn Factory consumes", StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    Application.Current.Dispatcher?.BeginInvoke(new Action(() => MarkFirmwareProgressStepCompleted("Factory")));
                    continue;
                }

                if (segment.IndexOf("Erase Chip", StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    Application.Current.Dispatcher?.BeginInvoke(new Action(() => MarkFirmwareProgressStepCompleted("ERASE_CHIP")));
                }

                bool isProgressLine = segment.IndexOf("[BURN", StringComparison.OrdinalIgnoreCase) >= 0 || segment.IndexOf("[ERASE_CHIP", StringComparison.OrdinalIgnoreCase) >= 0;
                if (!isProgressLine)
                {
                    continue;
                }

                foreach (string stepId in _firmwareProgressStepIds)
                {
                    if (_firmwareProgressCompletedIds.Contains(stepId))
                    {
                        continue;
                    }

                    string candidateStep = stepId;
                    if (segment.IndexOf(candidateStep, StringComparison.OrdinalIgnoreCase) >= 0)
                    {
                        Application.Current.Dispatcher?.BeginInvoke(new Action(() => MarkFirmwareProgressStepCompleted(candidateStep)));
                        break;
                    }
                }
            }
        }

        private void MarkFirmwareProgressStepCompleted(string identifier)
        {
            if (string.IsNullOrWhiteSpace(identifier) || !_firmwareProgressStepIds.Contains(identifier))
            {
                return;
            }

            if (!_firmwareProgressCompletedIds.Add(identifier))
            {
                return;
            }

            UpdateBurnProgressDisplay();
        }

        private void UpdateBurnProgressDisplay()
        {
            if (_firmwareProgressTotalSteps <= 0)
            {
                BurnProgressValue = 0;
                BurnProgressText = string.Empty;
                BurnProgressVisibility = Visibility.Visible;
                return;
            }

            double percent = _firmwareProgressCompletedIds.Count / (double)_firmwareProgressTotalSteps * 100.0;
            BurnProgressValue = percent;
            BurnProgressText = string.Format("{0:0}% ({1}/{2})", percent, _firmwareProgressCompletedIds.Count, _firmwareProgressTotalSteps);
            if (BurnProgressVisibility != Visibility.Visible)
            {
                BurnProgressVisibility = Visibility.Visible;
            }
        }

        private void ResetFirmwareProgressPlan(bool resetUi)
        {
            _firmwareProgressOrder.Clear();
            _firmwareProgressStepIds.Clear();
            _firmwareProgressCompletedIds.Clear();
            _firmwareProgressTotalSteps = 0;
            _lastFirmwareFilesUsed.Clear();
            _lastFirmwareFilesSet.Clear();
            BurnProgressBrush = Brushes.SteelBlue;
            BurnProgressIndeterminate = false;
            BurnProgressVisibility = Visibility.Visible;
            if (resetUi)
            {
                BurnProgressValue = 0;
                BurnProgressText = string.Empty;
            }
        }

        private void FinalizeFirmwareProgressPlan()
        {
            _firmwareProgressTotalSteps = _firmwareProgressOrder.Count;
            _firmwareProgressCompletedIds.Clear();
            BurnProgressIndeterminate = false;
            BurnProgressVisibility = Visibility.Visible;
            if (_firmwareProgressTotalSteps > 0)
            {
                BurnProgressValue = 0;
                BurnProgressText = string.Format("0% (0/{0})", _firmwareProgressTotalSteps);
            }
            else
            {
                BurnProgressText = string.Empty;
                BurnProgressValue = 0;
            }
        }

        private void RegisterFirmwareProgressStep(string identifier)
        {
            if (string.IsNullOrWhiteSpace(identifier))
            {
                return;
            }

            if (_firmwareProgressStepIds.Add(identifier))
            {
                _firmwareProgressOrder.Add(identifier);
            }
        }

        private void MarkNextFirmwareProgressStepFromQueue()
        {
            foreach (string stepId in _firmwareProgressOrder)
            {
                if (!_firmwareProgressCompletedIds.Contains(stepId))
                {
                    MarkFirmwareProgressStepCompleted(stepId);
                    break;
                }
            }
        }

        private void RegisterFileProgressStep(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                return;
            }

            string fileName = Path.GetFileName(path);
            if (!ShouldTrackProgressForFile(fileName))
            {
                return;
            }

            RegisterFirmwareProgressStep(fileName);
            TrackFirmwareFileUsed(path);
        }

        private void TrackFirmwareFileUsed(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                return;
            }

            if (_lastFirmwareFilesSet.Add(path))
            {
                _lastFirmwareFilesUsed.Add(path);
            }
        }

        private void LogFirmwareFilesUsed()
        {
            if (_lastFirmwareFilesUsed.Count == 0)
            {
                return;
            }

            var sb = new StringBuilder();
            sb.AppendLine("Firmware files used:");
            foreach (string file in _lastFirmwareFilesUsed)
            {
                sb.AppendLine(file);
            }
            AppendBurnModeLog(sb.ToString().TrimEnd());
        }

        private void CompleteFirmwareProgressTracking()
        {
            if (_firmwareProgressTotalSteps <= 0)
            {
                return;
            }

            foreach (string stepId in _firmwareProgressStepIds)
            {
                _firmwareProgressCompletedIds.Add(stepId);
            }

            UpdateBurnProgressDisplay();
        }

        private void SetBurnProgressFinalState(Brush brush)
        {
            BurnProgressIndeterminate = false;
            BurnProgressBrush = brush ?? Brushes.SteelBlue;
            BurnProgressValue = 100;
            BurnProgressText = BurnModeStatusText;
            BurnProgressVisibility = Visibility.Visible;
        }

        private bool ShouldTrackProgressForFile(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
            {
                return false;
            }

            return !fileName.StartsWith("programmer", StringComparison.OrdinalIgnoreCase);
        }

        #endregion

        private string ApplySnStep(string sn, int stepIndex)
        {
            if (string.IsNullOrWhiteSpace(sn))
            {
                return sn;
            }

            if (!Regex.IsMatch(sn, @"^[A-Za-z0-9]{20}$"))
            {
                return sn;
            }

            // only operate when last 5 chars numeric
            string prefix = sn.Substring(0, 15);
            string suffix = sn.Substring(15);
            if (!Regex.IsMatch(suffix, @"^\d{5}$"))
            {
                return sn;
            }

            try
            {
                int value = int.Parse(suffix);
                switch (stepIndex)
                {
                    case 0:
                        return sn;
                    case 1:
                        value += 1;
                        if (value > 99999) value = 0;
                        break;
                    case 2:
                        if (value == 0)
                        {
                            value = 99999;
                        }
                        else
                        {
                            value -= 1;
                        }
                        break;
                    default:
                        return sn;
                }
                string newSuffix = value.ToString("D5");
                return prefix + newSuffix;
            }
            catch
            {
                return sn;
            }
        }

        private void ManualSnStep(int delta)
        {
            ProductionSn = ApplySnStep(ProductionSn, delta > 0 ? 1 : 2);
        }

        private void ManualAddressStep(ref string field, int delta, string propertyName)
        {
            if (string.IsNullOrWhiteSpace(field))
            {
                return;
            }
            string cleaned = field.Replace(":", string.Empty).Replace("-", string.Empty).Trim();
            if (!Regex.IsMatch(cleaned, @"^[0-9A-Fa-f]{12}$"))
            {
                return;
            }
            try
            {
                ulong value = Convert.ToUInt64(cleaned, 16);
                if (delta > 0)
                {
                    value += 1;
                }
                else if (delta < 0 && value > 0)
                {
                    value -= 1;
                }
                string formatted = FormatMacForDisplay(value.ToString("X").PadLeft(12, '0'));
                field = formatted;
                RaisePropertyChanged(propertyName);
            }
            catch
            {
                // ignore
            }
        }

        private void AppendProductionLog(string message)
        {
            if (!IsProductionMode)
            {
                return;
            }

            App.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                _productionReceiveLines.Enqueue(message);
                while (_productionReceiveLines.Count > MaxProductionReceiveLines)
                {
                    _productionReceiveLines.Dequeue();
                }
                ProductionReceiveLog = string.Join(Environment.NewLine, _productionReceiveLines);
            }));
        }

        private void ClearProductionReceiveLog()
        {
            _productionReceiveLines.Clear();
            ProductionReceiveLog = string.Empty;
        }

        #endregion

        #region UDP

        private UDPPara _UDPPara;

        public UDPPara UDPPara
        {
            get { return _UDPPara; }
            set { _UDPPara = value;
                RaisePropertyChanged();
            }
        }


        private Visibility _UDPLeftSetingVisual = Visibility.Visible;
        public Visibility UDPLeftSetingVisual
        {
            get => _UDPLeftSetingVisual;
            set
            {
                _UDPLeftSetingVisual = value;
                RaisePropertyChanged();
            }
        }

        private ReceivePara _UDPReceivePara;

        public ReceivePara UDPReceivePara
        {
            get { return _UDPReceivePara; }
            set { _UDPReceivePara = value; 
            RaisePropertyChanged();
            }
        }

        private SendPara _UDPSendPara;

        public SendPara UDPSendPara
        {
            get { return _UDPSendPara; }
            set { _UDPSendPara = value;
            RaisePropertyChanged() ;
            }
        }


        #endregion
    }
}


